using ReserveManagementSystemApi.Helpers; using ReserveManagementSystemLib.Models; namespace ReserveManagementSystemApi.Services { public class LoanCalculationService : ILoanCalculationService { private readonly LoanCalculationHelper _loanCalculationHelper; private readonly BoatLoanCalculationHelper _boatLoanCalculationHelper; private CalculationConfig _calculationConfig; public LoanCalculationService() { _loanCalculationHelper = new LoanCalculationHelper(); _boatLoanCalculationHelper = new BoatLoanCalculationHelper(); } public LoanReserve CalculateSingleReserve(Loan loan) { var result = new LoanReserve(); try { _calculationConfig = new CalculationConfig(loan); if (!loan.IsBoatLoan(loan)) { result = ReserveCalculation(loan); } else { result = BoatReserveCalculation(loan); } } catch (Exception e) { Log.Error("Failure to Calculate Loan: {0} {1}", e.Message, e.StackTrace); result.ReserveTypeMessage = "Exception while calculating reserve"; } return result; } public LoanChargeback CalculateSingleChargeback(Loan loan) { var result = new LoanChargeback(); try { _calculationConfig = new CalculationConfig(loan); if (!loan.IsBoatLoan(loan)) { result = ChargebackCalculation(loan); } else { result = BoatChargebackCalculation(loan); } } catch (Exception e) { Log.Error("Failure to Calculate Chargeback: {0} {1}", e.Message, e.StackTrace); } return result; } public Loan CalculateLoanReserve(Loan loan, decimal? originalReserve = null, DateTime? originalExportReserveDate = null) { try { _calculationConfig = new CalculationConfig(loan); var loanReserve = new LoanReserve(); if (!loan.IsBoatLoan(loan)) { loanReserve = ReserveCalculation(loan); } else { loanReserve = BoatReserveCalculation(loan); } loan = AssignReserveValues(loan, loanReserve); if (originalReserve.HasValue) { CalculateReserveAdjustment(loan, originalReserve, originalExportReserveDate); } } catch (Exception e) { Log.Error("Failure to Calculate Reserve: {0} {1}", e.Message, e.StackTrace); loan.ReserveMessage = "Exception while calculating reserve"; } return loan; } private static void CalculateReserveAdjustment(Loan loan, decimal? originalReserve, DateTime? originalExportReserveDate) { var updatedReserveSaved = Math.Round(loan.ReserveAmount, 4, MidpointRounding.AwayFromZero); //this represents the amount saved to DB var updatedReserveRounded = Math.Round(updatedReserveSaved, 2, MidpointRounding.AwayFromZero); var originalReserveRounded = Math.Round(originalReserve.Value, 2, MidpointRounding.AwayFromZero); if (updatedReserveRounded != originalReserveRounded) { loan.Adjustment = (updatedReserveRounded - originalReserveRounded); loan.ExportedReserveDate = null; //we only want to remove last export date if reserve amount changed } else { loan.ExportedReserveDate = originalExportReserveDate; } } private LoanReserve ReserveCalculation(Loan loanData) { var loanReserve = new LoanReserve(); _calculationConfig.BuyFinanceCharge = _loanCalculationHelper.FinanceCharge(_calculationConfig.LoanData().Term, loanData.LoanAmount, loanData.BuyRate); // Finance charge using BuyRate% _calculationConfig.ContractFinanceCharge = _loanCalculationHelper.FinanceCharge(_calculationConfig.LoanData().Term, loanData.LoanAmount, loanData.ContractRate); // Finance charge using ContractRate% if (loanData.ContractRate < loanData.BuyRate) { //Calculate Buy Down. return _loanCalculationHelper.CalculateBuyDown(_calculationConfig); } else { // Calculate Reserve Amount for all other loans. if ((double)loanData.ContractRate == (double)loanData.BuyRate) { loanReserve = _loanCalculationHelper.CalculateNoMarkup(_calculationConfig); } else { loanReserve = _loanCalculationHelper.CalculateStandardMarkup(_calculationConfig); } //Assign any Flat Fees if applicable return _loanCalculationHelper.AssignFlatFee(loanReserve, _calculationConfig); } } private LoanReserve BoatReserveCalculation(Loan loanData) { if (loanData.ContractRate < loanData.BuyRate) { _calculationConfig.BuyFinanceCharge = _loanCalculationHelper.FinanceCharge(_calculationConfig.LoanData().Term, loanData.LoanAmount, loanData.BuyRate); // Finance charge using BuyRate% _calculationConfig.ContractFinanceCharge = _loanCalculationHelper.FinanceCharge(_calculationConfig.LoanData().Term, loanData.LoanAmount, loanData.ContractRate); // Finance charge using ContractRate% return _loanCalculationHelper.CalculateBuyDown(_calculationConfig); } else { // Calculate Reserve Amount for all other loans. return _boatLoanCalculationHelper.CalculateStandardMarkup(_calculationConfig); } } private Loan AssignReserveValues(Loan loan, LoanReserve loanReserve) { loan.ReserveAmount = loanReserve.ReserveAmount; loan.ReserveMessage = loanReserve.ReserveTypeMessage; loan.Term = _calculationConfig.LoanData().Term; loan.FlatFeePaid = loanReserve.FlatFeePaid; return loan; } private LoanChargeback ChargebackCalculation(Loan loanData) { if (loanData.ReserveAmount < 0 || loanData.ReserveMessage != null && loanData.ReserveMessage.ToUpper().Contains("BUYDOWN")) { return SetBuyDownChargeback(loanData); } if (loanData.LoanType?.ChargebackMethod == "S") { return CalculateBBLoans(loanData); } else { return CalculateLoanWithChargebackDate(loanData); } } private LoanChargeback BoatChargebackCalculation(Loan loanData) { if (loanData.ReserveAmount < 0 || loanData.ReserveMessage != null && loanData.ReserveMessage.ToUpper().Contains("BUYDOWN")) { return SetBuyDownChargeback(loanData); } else { return CalculateBoatLoanWithChargebackDate(loanData); } } private LoanChargeback CalculateBoatLoanWithChargebackDate(Loan loanData) { var numOfChargebackPayments = loanData.Term <= 63 ? 6 : 12; //term is 63 or less chargeback date is 6 monthly payments, otherwise 12 var lastChargebackDate = _boatLoanCalculationHelper.GetLastChargebackDate(loanData.FirstPmtDate, numOfChargebackPayments); if (loanData.PaidLoanDate > lastChargebackDate) { return new LoanChargeback { ChargebackAmount = 0M, ChargebackTypeMessage = loanData.ReserveAmount == 0 ? "No Reserve - No Chargeback" : "Paid After Chargeback Date", RemainingMonthsAtClose = (byte)_loanCalculationHelper.GetRemainingMonths(loanData) }; } return ChargebackWithRemainingMonths(loanData); } private LoanChargeback CalculateLoanWithChargebackDate(Loan loanData) { var numOfChargebackPayments = loanData.LoanType?.NoChargebackPayments ?? 3; var numOfChargebackDays = loanData.LoanType?.NoChargebackDays ?? 90; var lastChargebackDate = _loanCalculationHelper.GetLastChargebackDate(loanData.LoanDate, loanData.FirstPmtDate, numOfChargebackPayments, numOfChargebackDays); if (loanData.PaidLoanDate > lastChargebackDate) { return new LoanChargeback { ChargebackAmount = 0M, ChargebackTypeMessage = loanData.ReserveAmount == 0 ? "No Reserve - No Chargeback" : "Paid After Chargeback Date", RemainingMonthsAtClose = (byte)_loanCalculationHelper.GetRemainingMonths(loanData) }; } return ChargebackWithRemainingMonths(loanData); } private LoanChargeback ChargebackWithRemainingMonths(Loan loanData) { int remainingMonths = 0; string message = "Paid On or Before Chargeback Date"; remainingMonths = _loanCalculationHelper.GetRemainingMonths(loanData); return new LoanChargeback { //this needs to be the original term formula ChargebackAmount = _loanCalculationHelper.CalculateChargeback(loanData.ReserveAmount, loanData.Term, remainingMonths), ChargebackTypeMessage = loanData.ReserveAmount == 0 ? "No Reserve - No Chargeback" : message, RemainingMonthsAtClose = (byte)remainingMonths }; } private LoanChargeback ChargebackWithRemainingMonthsBB(Loan loanData) { int remainingMonths = 0; string message = "Paid After Chargeback Date"; //add chargeback date for BB Loans var chargeBackDate = loanData.FirstPmtDate.AddMonths(loanData.Term - 1); if (loanData.PaidLoanDate <= chargeBackDate) { remainingMonths = _loanCalculationHelper.GetRemainingMonths(loanData); if (remainingMonths <= 0) { return new LoanChargeback { ChargebackAmount = 0M, ChargebackTypeMessage = message, RemainingMonthsAtClose = 0 }; } else { message = "Paid On or Before Chargeback Date"; } } return new LoanChargeback { ChargebackAmount = _loanCalculationHelper.CalculateChargeback(loanData.ReserveAmount, loanData.Term, remainingMonths), ChargebackTypeMessage = message, RemainingMonthsAtClose = (byte)remainingMonths }; } private LoanChargeback CalculateBBLoans(Loan loanData) { if (loanData.ReserveAmount != 0) { return ChargebackWithRemainingMonthsBB(loanData); } else { return new LoanChargeback { ChargebackAmount = 0M, ChargebackTypeMessage = "No Reserve - No Chargeback", RemainingMonthsAtClose = (byte)_loanCalculationHelper.GetRemainingMonths(loanData) }; } } private LoanChargeback SetBuyDownChargeback(Loan loanData) { //Buydown scenarios will not calculate a chargeback and return a message indicating this. return new LoanChargeback { ChargebackAmount = 0M, ChargebackTypeMessage = "Buy Down Reserve - No Chargeback", RemainingMonthsAtClose = (byte)_loanCalculationHelper.GetRemainingMonths(loanData) }; } } }