Adding
This commit is contained in:
+448
@@ -0,0 +1,448 @@
|
||||
using System.Linq.Dynamic.Core;
|
||||
using ReserveManagementSystemApi.Enums;
|
||||
using ReserveManagementSystemApi.ServiceClients;
|
||||
using ReserveManagementSystemLib.Helpers;
|
||||
using ReserveManagementSystemLib.Models;
|
||||
using ReserveManagementSystemLib.Repositories;
|
||||
|
||||
namespace ReserveManagementSystemApi.Services
|
||||
{
|
||||
public class LoanService : ILoanService
|
||||
{
|
||||
private readonly ILoanRepository _loanRepository;
|
||||
private readonly ILoanTypeRepository _loanTypeRepository;
|
||||
private readonly IAuditHistoryService _auditHistoryService;
|
||||
private ILoanCalculationService _loanCalculationService;
|
||||
private const double MonthInDays = 30.4375;
|
||||
|
||||
private readonly ICoreApiServiceClient _coreApiServiceClient;
|
||||
|
||||
public LoanService(ILoanRepository loanRepository, ILoanTypeRepository loanTypeRepository, IAuditHistoryService auditHistoryService, ICoreApiServiceClient coreApiServiceClient)
|
||||
{
|
||||
_loanRepository = loanRepository;
|
||||
_loanTypeRepository = loanTypeRepository;
|
||||
_auditHistoryService = auditHistoryService;
|
||||
_coreApiServiceClient = coreApiServiceClient;
|
||||
_loanCalculationService = new LoanCalculationService();
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Loan>> GetAllAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _loanRepository.GetAll().OrderByDescending(x => x.ImportId).ThenBy(y => y.MemberNumber).ThenBy(z => z.LoanNumber).ToListAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving loans.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Loan> GetByIdAsync(int id)
|
||||
{
|
||||
Loan loan;
|
||||
try
|
||||
{
|
||||
loan = await _loanRepository.FindByIdAsync(id);
|
||||
if (loan != null)
|
||||
{
|
||||
loan.ValidationErrors = ValidationHelper.SetValidationErrors(loan);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving loan with id {id}.");
|
||||
throw;
|
||||
}
|
||||
|
||||
if (loan == null)
|
||||
{
|
||||
throw new Exception($"Loan with id {id} not found.");
|
||||
}
|
||||
|
||||
return loan;
|
||||
}
|
||||
|
||||
public async Task<PaginatedListResponse> GetFilteredAsync(LoanFilter filter)
|
||||
{
|
||||
try
|
||||
{
|
||||
IQueryable<Loan> loans;
|
||||
PaginatedListResponse pagedListResponse = new PaginatedListResponse();
|
||||
var query = _loanRepository.GetAll();
|
||||
var filteredQuery = filter.ApplyFilter(query);
|
||||
|
||||
List<string> listOfFieldNames = typeof(Loan).GetProperties().Select(f => f.Name).ToList();
|
||||
listOfFieldNames.AddRange(typeof(Dealer).GetProperties().Select(f => $"Dealer.{f.Name}").ToList());
|
||||
listOfFieldNames.AddRange(typeof(Vehicle).GetProperties().Select(f => $"Vehicle.{f.Name}").ToList());
|
||||
|
||||
var result = listOfFieldNames.Where(n => n == filter.SortField).FirstOrDefault();
|
||||
|
||||
if (!string.IsNullOrEmpty(filter.SortField) && result != null)
|
||||
{
|
||||
loans = filteredQuery.OrderBy($"{filter.SortField} {filter.SortDirection}, ImportId desc, MemberNumber, LoanNumber");
|
||||
}
|
||||
else
|
||||
{
|
||||
loans = filteredQuery.OrderBy($"ImportId desc, MemberNumber, LoanNumber");
|
||||
}
|
||||
pagedListResponse = await PaginatedListResponse.CreateListAsync(loans, filter.PageIndex, filter.MaxRecordsPerPage, filter.MaxPagesPerRange);
|
||||
return pagedListResponse;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving loans with filter.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<LoanReserve> GetLoanReserveAsync(LoanReserveInput input)
|
||||
{
|
||||
var result = new LoanReserve();
|
||||
var loan = new Loan
|
||||
{
|
||||
LoanDate = input.LoanDate,
|
||||
FirstPmtDate = input.FirstPmtDate,
|
||||
LoanAmount = input.LoanAmount,
|
||||
LoanTypeId = input.LoanType,
|
||||
BuyRate = input.BuyRate,
|
||||
ContractRate = input.ContractRate,
|
||||
Term = (int)Math.Round(Convert.ToDecimal(input.OriginalTermInDays) / (decimal)MonthInDays, 0),
|
||||
AccountType = input.AccountType
|
||||
};
|
||||
|
||||
loan.LoanType = await _loanTypeRepository.GetLoanTypeAsync(loan.LoanTypeId);
|
||||
try
|
||||
{
|
||||
_loanCalculationService = new LoanCalculationService();
|
||||
result = _loanCalculationService.CalculateSingleReserve(loan);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("Failure to Calculate Loan Reserve: {0} {1}", e.Message, e.StackTrace);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<LoanChargeback> GetLoanChargebackAsync(LoanChargebackInput input)
|
||||
{
|
||||
var result = new LoanChargeback();
|
||||
var loan = new Loan
|
||||
{
|
||||
LoanDate = input.LoanDate,
|
||||
FirstPmtDate = input.FirstPmtDate,
|
||||
FinalPmtDate = input.FinalPmtDate,
|
||||
LoanTypeId = input.LoanType,
|
||||
ReserveAmount = input.ReserveAmount,
|
||||
PaidLoanDate = input.PaidLoanDate,
|
||||
Term = input.Term,
|
||||
AccountType = input.AccountType
|
||||
};
|
||||
|
||||
loan.LoanType = await _loanTypeRepository.GetLoanTypeAsync(loan.LoanTypeId);
|
||||
try
|
||||
{
|
||||
result = _loanCalculationService.CalculateSingleChargeback(loan);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("Failure to Calculate Loan Chargeback: {0} {1}", e.Message, e.StackTrace);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<List<Loan>> SaveLoansAsync(List<Loan> loans)
|
||||
{
|
||||
var loansToAddOrUpdate = new List<Loan>();
|
||||
var historyRecords = new List<AuditHistory>();
|
||||
var loanTypes = await _loanTypeRepository.GetLoanTypesAsync();
|
||||
var numberOfLoansToUpdateReserve = 0;
|
||||
var numberOfLoansToAdd = 0;
|
||||
var numberOfLoansToUpdate = 0;
|
||||
|
||||
try
|
||||
{
|
||||
Log.Information($"Have {loans.Count} loans to add or update");
|
||||
loans.ForEach(loan =>
|
||||
{
|
||||
var validationErrors = ValidationHelper.SetValidationErrors(loan);
|
||||
loan.ActionRequired = validationErrors.Count > 0;
|
||||
loan.LoanType = loanTypes.FirstOrDefault(x => x.LoanTypeId == loan.LoanTypeId);
|
||||
var originalLoan = _loanRepository.GetAll().FirstOrDefault(l => l.LoanNumber == loan.LoanNumber);
|
||||
|
||||
if (originalLoan != null) //have an updated loan
|
||||
{
|
||||
if (!loan.Equals(originalLoan) || (loan.Vehicle != null && !loan.Vehicle.Equals(originalLoan.Vehicle)))
|
||||
{
|
||||
numberOfLoansToUpdate++;
|
||||
loan.LoanId = originalLoan.LoanId;
|
||||
var needToUpdateReserve = !loan.HasSameReserve(originalLoan);
|
||||
if (needToUpdateReserve)
|
||||
{
|
||||
numberOfLoansToUpdateReserve++;
|
||||
loan = _loanCalculationService.CalculateLoanReserve(loan, originalLoan.ReserveAmount, originalLoan.ExportedReserveDate);
|
||||
}
|
||||
|
||||
historyRecords.Add(_auditHistoryService.GetExistingLoanUpdateHistoryAsync(loan, originalLoan));
|
||||
CopyFromDnaLoanToOriginalLoan(loan, originalLoan, needToUpdateReserve);
|
||||
|
||||
originalLoan.LoanStatus = LoanStatus.Updated.ToString();
|
||||
|
||||
loansToAddOrUpdate.Add(originalLoan);
|
||||
}
|
||||
}
|
||||
else //have a new loan
|
||||
{
|
||||
numberOfLoansToAdd++;
|
||||
loan = _loanCalculationService.CalculateLoanReserve(loan);
|
||||
|
||||
loan.Dealer = null;
|
||||
loan.LoanStatus = LoanStatus.New.ToString();
|
||||
loansToAddOrUpdate.Add(loan);
|
||||
}
|
||||
});
|
||||
|
||||
Log.Information($"{numberOfLoansToUpdate} loans were updated");
|
||||
Log.Information($"{numberOfLoansToUpdateReserve} loans had reserve recalculated");
|
||||
Log.Information($"{numberOfLoansToAdd} loans were added");
|
||||
|
||||
if (loansToAddOrUpdate.Any())
|
||||
{
|
||||
await _loanRepository.UpdateAsync(loansToAddOrUpdate);
|
||||
}
|
||||
if (historyRecords.Any())
|
||||
{
|
||||
await _auditHistoryService.AddHistoriesAsync(historyRecords);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("Failure to Save Loans: {0} {1}", e.Message, e.StackTrace);
|
||||
throw;
|
||||
}
|
||||
|
||||
return loansToAddOrUpdate;
|
||||
}
|
||||
|
||||
public async Task<List<Loan>> SaveWithChargebackAsync(List<Loan> loans)
|
||||
{
|
||||
var historiesForClosedLoans = new List<AuditHistory>();
|
||||
var historiesForLoansNotFound = new List<AuditHistory>();
|
||||
var loansToClose = new List<Loan>();
|
||||
var description = "Importing closed loan changed loan status";
|
||||
loans.ForEach(loan =>
|
||||
{
|
||||
var originalLoan = _loanRepository.GetAllWithoutDealer().FirstOrDefault(l => l.LoanNumber == loan.LoanNumber);
|
||||
|
||||
if (originalLoan != null) //have an updated loan
|
||||
{
|
||||
if (!originalLoan.PaidLoanDate.HasValue || originalLoan.PaidLoanDate != loan.PaidLoanDate)
|
||||
{
|
||||
var oldStatus = originalLoan.LoanStatus;
|
||||
originalLoan.PaidLoanDate = loan.PaidLoanDate;
|
||||
originalLoan.ImportId = loan.ImportId;
|
||||
var chargebackResult = _loanCalculationService.CalculateSingleChargeback(originalLoan);
|
||||
|
||||
AssignChargebackResult(originalLoan, chargebackResult);
|
||||
|
||||
historiesForClosedLoans.Add(_auditHistoryService.GetLoanStatusChangeHistory(originalLoan, oldStatus, description));
|
||||
|
||||
loansToClose.Add(originalLoan);
|
||||
}
|
||||
}
|
||||
else //active loan not found to close
|
||||
{
|
||||
//set this temporary status to flag selecting original loan with details in order to calculate both reserve and chargeback
|
||||
loan.LoanStatus = LoanStatus.MissingActiveLoan.ToString();
|
||||
loansToClose.Add(loan);
|
||||
historiesForLoansNotFound.Add(_auditHistoryService.GetAuditHistoryForNonexistingClosedLoan(loan));
|
||||
}
|
||||
});
|
||||
|
||||
await _auditHistoryService.AddHistoriesAsync(historiesForClosedLoans);
|
||||
await _auditHistoryService.AddHistoriesAsync(historiesForLoansNotFound);
|
||||
await _loanRepository.UpdateAsync(loansToClose.Where(l => l.LoanStatus != "MissingActiveLoan"));
|
||||
return loansToClose;
|
||||
}
|
||||
|
||||
/// <summary>This method was used for one time import, we want to save the original reserve info for an already closed loan pre Go-Live. </summary>
|
||||
public async Task<List<Loan>> SaveWithReserveAndChargebackAsync(List<Loan> loans)
|
||||
{
|
||||
try
|
||||
{
|
||||
var loanTypes = await _loanTypeRepository.GetLoanTypesAsync();
|
||||
var historiesForClosedLoans = new List<AuditHistory>();
|
||||
var loansToClose = new List<Loan>();
|
||||
var description = "Importing closed loan changed loan status";
|
||||
|
||||
loans.ForEach(loan =>
|
||||
{
|
||||
var originalLoan = _loanRepository.GetAllWithoutDealer().FirstOrDefault(l => l.LoanNumber == loan.LoanNumber);
|
||||
|
||||
if (originalLoan == null)
|
||||
{
|
||||
loan.LoanType = loanTypes.FirstOrDefault(x => x.LoanTypeId == loan.LoanTypeId);
|
||||
loan = _loanCalculationService.CalculateLoanReserve(loan, null, null);
|
||||
var chargebackResult = _loanCalculationService.CalculateSingleChargeback(loan);
|
||||
AssignChargebackResult(loan, chargebackResult);
|
||||
loansToClose.Add(loan);
|
||||
historiesForClosedLoans.Add(_auditHistoryService.GetLoanStatusChangeHistory(loan, "", description));
|
||||
}
|
||||
});
|
||||
|
||||
await _auditHistoryService.AddHistoriesAsync(historiesForClosedLoans);
|
||||
await _loanRepository.UpdateAsync(loansToClose);
|
||||
|
||||
return loansToClose;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex.Message);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<string>> SearchMembers(string searchText)
|
||||
{
|
||||
var memberNumbers = new List<string>();
|
||||
try
|
||||
{
|
||||
memberNumbers = await _loanRepository.GetAll().Where(x => x.MemberNumber.StartsWith(searchText))
|
||||
.Select(y => y.MemberNumber)
|
||||
.Distinct()
|
||||
.ToListAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving list of members.");
|
||||
throw;
|
||||
}
|
||||
|
||||
return memberNumbers.OrderBy(x => long.Parse(x)).ToList();
|
||||
}
|
||||
|
||||
public async Task<List<string>> SearchLoanNumbers(string searchText)
|
||||
{
|
||||
var loanNumbers = new List<string>();
|
||||
try
|
||||
{
|
||||
loanNumbers = await _loanRepository.GetAll().Where(x => x.LoanNumber.StartsWith(searchText))
|
||||
.Select(x => x.LoanNumber)
|
||||
.Distinct()
|
||||
.ToListAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving list of loan numbers.");
|
||||
throw;
|
||||
}
|
||||
|
||||
return loanNumbers.OrderBy(x => long.Parse(x)).ToList();
|
||||
}
|
||||
|
||||
public async Task<List<string>> SearchApplicationIds(string searchText)
|
||||
{
|
||||
var applicationIds = new List<string>();
|
||||
try
|
||||
{
|
||||
applicationIds = await _loanRepository.GetAll().Where(x => x.ApplicationId.StartsWith(searchText))
|
||||
.Select(x => x.ApplicationId)
|
||||
.Distinct()
|
||||
.ToListAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving list of application ids.");
|
||||
throw;
|
||||
}
|
||||
|
||||
return applicationIds.OrderBy(x => long.Parse(x)).ToList();
|
||||
}
|
||||
|
||||
public async Task UpdateDealerOriginalBalanceAsync(List<string> accountNumbers)
|
||||
{
|
||||
foreach(var account in accountNumbers)
|
||||
{
|
||||
var exportResult = await _coreApiServiceClient.PutDealerOriginalBalance(account);
|
||||
|
||||
if (exportResult != null)
|
||||
{
|
||||
foreach (var result in exportResult)
|
||||
if (!string.IsNullOrEmpty(result.ErrorMessage))
|
||||
{
|
||||
Log.Error($"Failure to update dealer original balance loan: {result.AccountNumber} {result.ErrorMessage} ");
|
||||
}
|
||||
}
|
||||
else //if nothing is returned all updates succeeded
|
||||
{
|
||||
Log.Information($"Update to dealers original balances completed successfully");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static void AssignChargebackResult(Loan loan, LoanChargeback chargebackResult, bool skipExport = false)
|
||||
{
|
||||
loan.ChargeBack = Math.Round(chargebackResult.ChargebackAmount, 4);
|
||||
loan.ChargebackMessage = chargebackResult.ChargebackTypeMessage;
|
||||
loan.RemainingMonthsAtClose = chargebackResult.RemainingMonthsAtClose;
|
||||
loan.ActionRequired = false;
|
||||
|
||||
//if skipping export then loan will already have ExportedChargebackDate set
|
||||
loan.ExportedChargebackDate = skipExport ? loan.ExportedChargebackDate : null;
|
||||
loan.LoanStatus = LoanStatus.Closed.ToString();
|
||||
loan.Dealer = null;
|
||||
}
|
||||
|
||||
private static void CopyFromDnaLoanToOriginalLoan(Loan loan, Loan originalLoan, bool needToUpdateReserve)
|
||||
{
|
||||
originalLoan.ActionRequired = loan.ActionRequired;
|
||||
originalLoan.ImportId = loan.ImportId;
|
||||
originalLoan.MemberNumber = loan.MemberNumber;
|
||||
originalLoan.MemberFirstName = loan.MemberFirstName;
|
||||
originalLoan.MemberInitial = loan.MemberInitial;
|
||||
originalLoan.MemberLastName = loan.MemberLastName;
|
||||
originalLoan.DealerOrganizationNumber = loan.DealerOrganizationNumber;
|
||||
originalLoan.LoanDate = loan.LoanDate;
|
||||
originalLoan.PostedDate = loan.PostedDate;
|
||||
originalLoan.FirstPmtDate = loan.FirstPmtDate;
|
||||
originalLoan.FinalPmtDate = loan.FinalPmtDate;
|
||||
originalLoan.LoanAmount = loan.LoanAmount;
|
||||
originalLoan.BuyRate = loan.BuyRate;
|
||||
originalLoan.ContractRate = loan.ContractRate;
|
||||
originalLoan.MonthlyPmt = loan.MonthlyPmt;
|
||||
originalLoan.LoanTypeId = loan.LoanTypeId;
|
||||
originalLoan.ApplicationId = loan.ApplicationId;
|
||||
//suppressing these insurance updates until DNA has correct value
|
||||
//originalLoan.Warranty = loan.Warranty;
|
||||
//originalLoan.Gap = loan.Gap;
|
||||
originalLoan.CreditLife = loan.CreditLife;
|
||||
originalLoan.CreditDisability = loan.CreditDisability;
|
||||
//originalLoan.Mbp = loan.Mbp;
|
||||
originalLoan.AccountType = loan.AccountType;
|
||||
|
||||
if (originalLoan.Vehicle != null)
|
||||
{
|
||||
originalLoan.Vehicle.VINNumber = loan.Vehicle?.VINNumber;
|
||||
originalLoan.Vehicle.Make = loan.Vehicle?.Make;
|
||||
originalLoan.Vehicle.Model = loan.Vehicle?.Model;
|
||||
originalLoan.Vehicle.Mileage = loan.Vehicle?.Mileage ?? 0;
|
||||
originalLoan.Vehicle.Year = loan.Vehicle?.Year;
|
||||
originalLoan.Vehicle.Color = loan.Vehicle?.Color;
|
||||
}
|
||||
|
||||
if (needToUpdateReserve)
|
||||
{
|
||||
originalLoan.ReserveAmount = loan.ReserveAmount;
|
||||
originalLoan.ReserveMessage = loan.ReserveMessage;
|
||||
originalLoan.ExportedReserveDate = loan.ExportedReserveDate;
|
||||
originalLoan.Term = loan.Term;
|
||||
originalLoan.FlatFeePaid = loan.FlatFeePaid;
|
||||
originalLoan.Adjustment = loan.Adjustment;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user