Adding
This commit is contained in:
+146
@@ -0,0 +1,146 @@
|
||||
using Newtonsoft.Json;
|
||||
using ReserveManagementSystemApi.Enums;
|
||||
using ReserveManagementSystemLib.Models;
|
||||
using ReserveManagementSystemLib.Repositories;
|
||||
|
||||
namespace ReserveManagementSystemApi.Services
|
||||
{
|
||||
public class AuditHistoryService : IAuditHistoryService
|
||||
{
|
||||
private readonly IAuditHistoryRepository _auditHistoryRepository;
|
||||
const string SYSTEM_MODIFIED_BY = "RMS";
|
||||
|
||||
public AuditHistoryService(IAuditHistoryRepository auditHistoryRepository)
|
||||
{
|
||||
_auditHistoryRepository = auditHistoryRepository;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<AuditHistory>> GetAllAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _auditHistoryRepository.GetAll().OrderByDescending(o => o.ModifiedDate).ToListAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving audit histories.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AuditHistory> FindByIdAsync(int id)
|
||||
{
|
||||
AuditHistory history;
|
||||
try
|
||||
{
|
||||
history = await _auditHistoryRepository.FindByIdAsync(id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving history with id {id}.");
|
||||
throw;
|
||||
}
|
||||
|
||||
if (history == null)
|
||||
{
|
||||
throw new Exception($"Audit history with id {id} not found.");
|
||||
}
|
||||
|
||||
return history;
|
||||
}
|
||||
|
||||
public async Task<AuditHistory> AddHistoryAsync(AuditHistory history)
|
||||
{
|
||||
return await _auditHistoryRepository.AddHistoryAsync(history);
|
||||
}
|
||||
|
||||
public async Task<int> AddAuditHistoriesForNonexistingLoansAsync(List<Loan> loans)
|
||||
{
|
||||
var currentDate = DateTime.Now;
|
||||
var histories = loans.Select(loan => new AuditHistory
|
||||
{
|
||||
LoanId = null,
|
||||
MemberNumber = loan.MemberNumber,
|
||||
LoanNumber = loan.LoanNumber,
|
||||
ModifiedBy = SYSTEM_MODIFIED_BY,
|
||||
ModifiedDate = currentDate,
|
||||
ActivityType = ActivityType.Error.ToString(),
|
||||
AuditDescription = "Loan in closed file not found",
|
||||
OldValue = null,
|
||||
NewValue = null
|
||||
}).ToList();
|
||||
|
||||
return await _auditHistoryRepository.AddHistoriesAsync(histories);
|
||||
}
|
||||
|
||||
public AuditHistory GetAuditHistoryForNonexistingClosedLoan(Loan loan)
|
||||
{
|
||||
var currentDate = DateTime.Now;
|
||||
return new AuditHistory
|
||||
{
|
||||
LoanId = null,
|
||||
MemberNumber = loan.MemberNumber,
|
||||
LoanNumber = loan.LoanNumber,
|
||||
ModifiedBy = SYSTEM_MODIFIED_BY,
|
||||
ModifiedDate = currentDate,
|
||||
ActivityType = ActivityType.Error.ToString(),
|
||||
AuditDescription = "Loan in closed import not found",
|
||||
OldValue = null,
|
||||
NewValue = null
|
||||
};
|
||||
}
|
||||
|
||||
public AuditHistory GetExistingLoanUpdateHistoryAsync(Loan loan, Loan originalLoan)
|
||||
{
|
||||
var currentDate = DateTime.Now;
|
||||
return new AuditHistory
|
||||
{
|
||||
LoanId = loan.LoanId,
|
||||
MemberNumber = loan.MemberNumber,
|
||||
LoanNumber = loan.LoanNumber,
|
||||
ModifiedBy = SYSTEM_MODIFIED_BY,
|
||||
ModifiedDate = currentDate,
|
||||
ActivityType = ActivityType.NewLoanUpdate.ToString(),
|
||||
AuditDescription = "Loan found in table, record updated",
|
||||
OldValue = JsonConvert.SerializeObject(originalLoan),
|
||||
NewValue = JsonConvert.SerializeObject(loan)
|
||||
};
|
||||
}
|
||||
|
||||
public AuditHistory GetLoanStatusChangeHistory(Loan loan, string oldStatus, string description)
|
||||
{
|
||||
return new AuditHistory
|
||||
{
|
||||
LoanId = loan.LoanId,
|
||||
MemberNumber = loan.MemberNumber,
|
||||
LoanNumber = loan.LoanNumber,
|
||||
ModifiedBy = SYSTEM_MODIFIED_BY,
|
||||
ModifiedDate = DateTime.Now,
|
||||
ActivityType = ActivityType.LoanStatusChange.ToString(),
|
||||
OldValue = oldStatus,
|
||||
NewValue = loan.LoanStatus,
|
||||
AuditDescription = description
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<int> AddHistoriesAsync(List<AuditHistory> histories)
|
||||
{
|
||||
return await _auditHistoryRepository.AddHistoriesAsync(histories);
|
||||
}
|
||||
|
||||
public async Task AddErrorHistoryAsync(string memberNumber, string loanNumber, string description)
|
||||
{
|
||||
var auditHist = new AuditHistory
|
||||
{
|
||||
MemberNumber = memberNumber,
|
||||
LoanNumber = loanNumber,
|
||||
ActivityType = ActivityType.Error.ToString(),
|
||||
ModifiedDate = DateTime.Now,
|
||||
AuditDescription = description,
|
||||
ModifiedBy = SYSTEM_MODIFIED_BY
|
||||
};
|
||||
|
||||
await _auditHistoryRepository.AddHistoryAsync(auditHist);
|
||||
}
|
||||
}
|
||||
}
|
||||
+100
@@ -0,0 +1,100 @@
|
||||
using ReserveManagementSystemLib.Models;
|
||||
using ReserveManagementSystemLib.Repositories;
|
||||
|
||||
namespace ReserveManagementSystemApi.Services
|
||||
{
|
||||
public class DealerService: IDealerService
|
||||
{
|
||||
private readonly IDealerRepository _dealerRepository;
|
||||
|
||||
public DealerService(IDealerRepository dealerRepository)
|
||||
{
|
||||
_dealerRepository = dealerRepository;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Dealer>> Search(string dealerSearchText)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _dealerRepository.GetAll().Where(d => d.DealerCode.Contains(dealerSearchText) || d.Name.Contains(dealerSearchText)).OrderBy(x => x.DealerCode).ToListAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving dealers.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Dealer> GetDealerByCodeAsync(string dealerCode)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _dealerRepository.FindByCodeAsync(dealerCode);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving dealer by code.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Dealer>> GetAllDealersAsync()
|
||||
{
|
||||
return await _dealerRepository.GetAll().ToListAsync();
|
||||
}
|
||||
|
||||
public async Task UpdateDealersAsync(List<Dealer> dealers)
|
||||
{
|
||||
try
|
||||
{
|
||||
var allDealers = await GetAllDealersAsync();
|
||||
var dealersToAdd = new List<Dealer>();
|
||||
var dealersToUpdate = new List<Dealer>();
|
||||
dealers.ForEach(d =>
|
||||
{
|
||||
var currentDealer = allDealers.FirstOrDefault(a => a.DealerOrganizationNumber == d.DealerOrganizationNumber);
|
||||
if (currentDealer != null && !currentDealer.Equals(d))
|
||||
{
|
||||
currentDealer.Name = d.Name ?? currentDealer.Name;
|
||||
currentDealer.DealerCode = d.DealerCode?.ToUpper() ?? currentDealer.DealerCode;
|
||||
currentDealer.DealerAccountNumber = d.DealerAccountNumber ?? currentDealer.DealerAccountNumber;
|
||||
currentDealer.DealerOrganizationNumber = d.DealerOrganizationNumber ?? currentDealer.DealerOrganizationNumber;
|
||||
dealersToUpdate.Add(currentDealer);
|
||||
}
|
||||
else if (currentDealer == null)
|
||||
{
|
||||
//write the new dealer record to set Action required to true if invalid
|
||||
dealersToAdd.Add(d);
|
||||
}
|
||||
});
|
||||
if (dealersToUpdate.Any())
|
||||
{
|
||||
await _dealerRepository.UpdateDealersAsync(dealersToUpdate);
|
||||
}
|
||||
if (dealersToAdd.Any())
|
||||
{
|
||||
await _dealerRepository.AddDealersAsync(dealersToAdd);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure updating dealers.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task UpdateDealersAsync(List<Loan> loans)
|
||||
{
|
||||
var dealers = loans.Select(l => new Dealer
|
||||
{
|
||||
DealerCode = l.Dealer.DealerCode,
|
||||
Name = l.Dealer.Name,
|
||||
DealerOrganizationNumber = l.DealerOrganizationNumber,
|
||||
DealerAccountNumber = l.Dealer.DealerAccountNumber
|
||||
})
|
||||
.GroupBy(d => d.DealerOrganizationNumber).Select(g => g.First()).ToList();
|
||||
|
||||
await UpdateDealersAsync(dealers);
|
||||
}
|
||||
}
|
||||
}
|
||||
+275
@@ -0,0 +1,275 @@
|
||||
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.CoreRequests;
|
||||
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Transaction;
|
||||
using ReserveManagementSystemApi.Mappers;
|
||||
using ReserveManagementSystemApi.Helpers;
|
||||
using ReserveManagementSystemApi.Models;
|
||||
using ReserveManagementSystemApi.ServiceClients;
|
||||
using ReserveManagementSystemLib.Models;
|
||||
using ReserveManagementSystemLib.Repositories;
|
||||
using ReserveManagementSystemLib.Configuration;
|
||||
|
||||
namespace ReserveManagementSystemApi.Services
|
||||
{
|
||||
public class ExportService: IExportService
|
||||
{
|
||||
private readonly ILoanRepository _loanRepository;
|
||||
private readonly ICoreApiServiceClient _coreApiServiceClient;
|
||||
private readonly IMinorAccountTypeRepository _minorAccountTypeRepository;
|
||||
private readonly RmsApiConfiguration _rmsApiConfig;
|
||||
|
||||
public ExportService(ILoanRepository loanRepository, ICoreApiServiceClient coreApiServiceClient, IMinorAccountTypeRepository minorAccountTypeRepository, RmsApiConfiguration rmsApiConfig)
|
||||
{
|
||||
_loanRepository = loanRepository;
|
||||
_coreApiServiceClient = coreApiServiceClient;
|
||||
_minorAccountTypeRepository = minorAccountTypeRepository;
|
||||
_rmsApiConfig = rmsApiConfig;
|
||||
}
|
||||
|
||||
public async Task<int> ExportReservesAsync(DateTime currentDate)
|
||||
{
|
||||
var numberOfLoansUpdated = 0;
|
||||
try
|
||||
{
|
||||
var loansToUpdate = await _loanRepository.GetAll().Where(l => l.ExportedReserveDate == null).ToListAsync();
|
||||
var groupedLoanRequests = loansToUpdate.Select(ExportMapper.MapLoanToIndirectLoanForReserve).ToList();
|
||||
var exportResult = await SendBatchListMonetaryTransactionRequests(groupedLoanRequests);
|
||||
|
||||
if (exportResult != null || exportResult.Any())
|
||||
{
|
||||
var loansExported = new List<Loan>();
|
||||
|
||||
loansToUpdate.ForEach(loan =>
|
||||
{
|
||||
var validLoan = exportResult.Where(e => e.AccountNumber.ToString() == loan.LoanNumber).FirstOrDefault();
|
||||
if (validLoan != null && validLoan.WasSuccessful)
|
||||
{
|
||||
loan.ExportedReserveDate = currentDate;
|
||||
numberOfLoansUpdated += 1;
|
||||
loan.Adjustment = 0;
|
||||
loansExported.Add(loan); //update successful loan export with exported date
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"Failure to export loan reserve account number: {loan.LoanNumber}");
|
||||
}
|
||||
});
|
||||
|
||||
await _loanRepository.UpdateAsync(loansExported);
|
||||
Log.Information($"Exported {numberOfLoansUpdated} reserves");
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure exporting loan reserves.");
|
||||
throw;
|
||||
}
|
||||
|
||||
return numberOfLoansUpdated;
|
||||
}
|
||||
|
||||
public async Task<int> ExportChargebacksAsync(DateTime currentDate)
|
||||
{
|
||||
var numberOfLoansUpdated = 0;
|
||||
try
|
||||
{
|
||||
var loansToUpdate = await _loanRepository.GetAll().Where(l => l.ExportedChargebackDate == null && l.PaidLoanDate != null).ToListAsync();
|
||||
var groupedLoanRequests = new List<GroupedMonetaryTransactionRequest>();
|
||||
loansToUpdate.ForEach(loan =>
|
||||
{
|
||||
var accountType = GetMinorAccountType(loan.AccountType);
|
||||
groupedLoanRequests.Add(ExportMapper.MapLoanToIndirectLoanForChargeback(loan, accountType));
|
||||
}
|
||||
);
|
||||
|
||||
var exportResult = await SendBatchListMonetaryTransactionRequests(groupedLoanRequests);
|
||||
|
||||
if (exportResult != null || exportResult.Any())
|
||||
{
|
||||
var loansExported = new List<Loan>();
|
||||
loansToUpdate.ForEach(loan =>
|
||||
{
|
||||
var accountType = GetMinorAccountType(loan.AccountType);
|
||||
var validLoan = exportResult.Where(e => e.AccountNumber.ToString() == accountType.GeneralLedgerAccountNumber).FirstOrDefault();
|
||||
if (validLoan != null && validLoan.WasSuccessful)
|
||||
{
|
||||
loan.ExportedChargebackDate = currentDate;
|
||||
numberOfLoansUpdated += 1;
|
||||
loan.Adjustment = 0;
|
||||
loansExported.Add(loan); //update successful loan export with exported date
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"Failure to export loan reserve account number: {loan.LoanNumber}");
|
||||
}
|
||||
});
|
||||
|
||||
await _loanRepository.UpdateAsync(loansExported);
|
||||
Log.Information($"Exported {numberOfLoansUpdated} chargebacks");
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error("Failure exporting loan chargebacks.");
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure exporting loan chargebacks.");
|
||||
throw;
|
||||
}
|
||||
|
||||
return numberOfLoansUpdated;
|
||||
}
|
||||
|
||||
public async Task<List<ImportBoatLoan>> ExportBoatLoansAsync(List<Loan> loans)
|
||||
{
|
||||
var loansExported = new List<ImportBoatLoan>();
|
||||
try
|
||||
{
|
||||
var reserveLoans = loans.Where(l => l.ReserveAmount != 0);
|
||||
if (reserveLoans != null || reserveLoans.Any())
|
||||
{
|
||||
var groupedReserveRequests = reserveLoans.Select(ExportMapper.MapLoanToIndirectLoanForReserve).ToList();
|
||||
var exportReserveResult = await SendBatchListMonetaryTransactionRequests(groupedReserveRequests);
|
||||
loansExported.AddRange(MapBoatExportResults(exportReserveResult, reserveLoans.ToList()));
|
||||
}
|
||||
|
||||
var chargebackLoans = loans.Where(l => l.ChargeBack != 0).ToList();
|
||||
if (chargebackLoans != null || chargebackLoans.Any())
|
||||
{
|
||||
var groupedLoanRequests = new List<GroupedMonetaryTransactionRequest>();
|
||||
chargebackLoans.ForEach(loan =>
|
||||
{
|
||||
var accountType = GetMinorAccountType(loan.AccountType);
|
||||
groupedLoanRequests.Add(ExportMapper.MapLoanToIndirectLoanForChargeback(loan, accountType));
|
||||
}
|
||||
);
|
||||
|
||||
var exportResult = await _coreApiServiceClient.PostMonetaryTransactions(groupedLoanRequests);
|
||||
loansExported.AddRange(MapBoatExportResults(exportResult, chargebackLoans.ToList()));
|
||||
}
|
||||
}
|
||||
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure exporting boat loans.");
|
||||
throw;
|
||||
}
|
||||
|
||||
return loansExported;
|
||||
}
|
||||
|
||||
public async Task<int> MarkReservesAsExportedAsync(List<Loan> loans, DateTime currentDate)
|
||||
{
|
||||
try
|
||||
{
|
||||
loans.ForEach(l =>
|
||||
{
|
||||
l.ExportedReserveDate = currentDate;
|
||||
});
|
||||
|
||||
await _loanRepository.UpdateAsync(loans);
|
||||
Log.Information($"Marked {loans.Count} loan reserves as exported (without exporting)");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure marking loan reserves as exported.");
|
||||
throw;
|
||||
}
|
||||
|
||||
return loans.Count;
|
||||
}
|
||||
|
||||
public async Task<int> MarkReservesAndChargebacksAsExportedAsync(List<Loan> loans, DateTime currentDate)
|
||||
{
|
||||
try
|
||||
{
|
||||
loans.ForEach(l =>
|
||||
{
|
||||
l.ExportedReserveDate = currentDate;
|
||||
l.ExportedChargebackDate = currentDate;
|
||||
});
|
||||
|
||||
await _loanRepository.UpdateAsync(loans);
|
||||
Log.Information($"Marked {loans.Count} loan reserves and chargebacks as exported (without exporting)");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure marking loan reserves and chargebacks as exported.");
|
||||
throw;
|
||||
}
|
||||
|
||||
return loans.Count;
|
||||
}
|
||||
|
||||
private List<ImportBoatLoan> MapBoatExportResults(List<MonetaryTransactionResponse> exportResult, List<Loan> loansToUpdate)
|
||||
{
|
||||
var numberOfLoansUpdated = 0;
|
||||
var loansExported = new List<ImportBoatLoan>();
|
||||
|
||||
if (exportResult != null || exportResult.Any())
|
||||
{
|
||||
loansToUpdate.ForEach(loan =>
|
||||
{
|
||||
var boatLoan = new ImportBoatLoan();
|
||||
string acccountNumber;
|
||||
if (loan.ReserveAmount != 0)
|
||||
{
|
||||
acccountNumber = loan.LoanNumber;
|
||||
}
|
||||
else
|
||||
{
|
||||
var accountType = GetMinorAccountType(loan.AccountType);
|
||||
acccountNumber = accountType.GeneralLedgerAccountNumber;
|
||||
}
|
||||
|
||||
var validLoan = exportResult.Where(e => e.AccountNumber.ToString() == acccountNumber).Any();
|
||||
var validDealer = exportResult.Where(e => e.AccountNumber.ToString() == loan.Dealer.DealerAccountNumber).Any();
|
||||
|
||||
if (validLoan && validDealer)
|
||||
{
|
||||
numberOfLoansUpdated += 1;
|
||||
boatLoan.LoanNumber = loan.LoanNumber;
|
||||
boatLoan.WasSuccessful = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var error = $"Failure to export boat loan account number: {loan.LoanNumber}";
|
||||
boatLoan.LoanNumber = loan.LoanNumber;
|
||||
boatLoan.WasSuccessful = false;
|
||||
boatLoan.ErrorMessage = error;
|
||||
Log.Error(error);
|
||||
}
|
||||
|
||||
loansExported.Add(boatLoan);
|
||||
});
|
||||
|
||||
Log.Information($"Exported {numberOfLoansUpdated} boat loans");
|
||||
}
|
||||
|
||||
return loansExported;
|
||||
}
|
||||
|
||||
private MinorAccountType GetMinorAccountType(string loanAccountType)
|
||||
{
|
||||
var result = _minorAccountTypeRepository.GetMinorAccountType(loanAccountType);
|
||||
return result;
|
||||
}
|
||||
|
||||
public async Task<List<MonetaryTransactionResponse>> SendBatchListMonetaryTransactionRequests(List<GroupedMonetaryTransactionRequest> groupedLoanRequests)
|
||||
{
|
||||
var result = new List<MonetaryTransactionResponse>();
|
||||
var batchSize = _rmsApiConfig.DefaultBatchSize;
|
||||
|
||||
foreach (var batch in BatchHelper.CreateBatch<GroupedMonetaryTransactionRequest>(groupedLoanRequests, batchSize))
|
||||
{
|
||||
var response = await _coreApiServiceClient.PostMonetaryTransactions(batch.ToList());
|
||||
result.AddRange(response);
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
using ReserveManagementSystemLib.Models;
|
||||
|
||||
namespace ReserveManagementSystemApi.Services
|
||||
{
|
||||
public interface IAuditHistoryService
|
||||
{
|
||||
Task<IEnumerable<AuditHistory>> GetAllAsync();
|
||||
|
||||
Task<AuditHistory> FindByIdAsync(int id);
|
||||
|
||||
Task<AuditHistory> AddHistoryAsync(AuditHistory history);
|
||||
|
||||
Task<int> AddAuditHistoriesForNonexistingLoansAsync(List<Loan> loans);
|
||||
|
||||
AuditHistory GetExistingLoanUpdateHistoryAsync(Loan loan, Loan originalLoan);
|
||||
|
||||
AuditHistory GetAuditHistoryForNonexistingClosedLoan(Loan loan);
|
||||
|
||||
AuditHistory GetLoanStatusChangeHistory(Loan loan, string oldStatus, string description);
|
||||
|
||||
Task<int> AddHistoriesAsync(List<AuditHistory> histories);
|
||||
|
||||
Task AddErrorHistoryAsync(string memberNumber, string loanNumber, string description);
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
using ReserveManagementSystemLib.Models;
|
||||
|
||||
namespace ReserveManagementSystemApi.Services
|
||||
{
|
||||
public interface IDealerService
|
||||
{
|
||||
Task<IEnumerable<Dealer>> Search(string dealerSearch);
|
||||
|
||||
Task<Dealer> GetDealerByCodeAsync(string dealerCode);
|
||||
|
||||
Task UpdateDealersAsync(List<Dealer> dealers);
|
||||
|
||||
Task UpdateDealersAsync(List<Loan> loans);
|
||||
|
||||
Task<IEnumerable<Dealer>> GetAllDealersAsync();
|
||||
}
|
||||
}
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
using ReserveManagementSystemApi.Models;
|
||||
using ReserveManagementSystemLib.Models;
|
||||
|
||||
namespace ReserveManagementSystemApi.Services
|
||||
{
|
||||
public interface IExportService
|
||||
{
|
||||
Task<int> ExportReservesAsync(DateTime currentDate);
|
||||
|
||||
Task<int> ExportChargebacksAsync(DateTime currentDate);
|
||||
|
||||
Task<List<ImportBoatLoan>> ExportBoatLoansAsync(List<Loan> loans);
|
||||
|
||||
Task<int> MarkReservesAsExportedAsync(List<Loan> loans, DateTime currentDate);
|
||||
|
||||
Task<int> MarkReservesAndChargebacksAsExportedAsync(List<Loan> loans, DateTime currentDate);
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.IndirectLoan;
|
||||
using ReserveManagementSystemApi.Models;
|
||||
using ReserveManagementSystemLib.Models;
|
||||
|
||||
namespace ReserveManagementSystemApi.Services
|
||||
{
|
||||
public interface IImportReadService
|
||||
{
|
||||
Task<Import> ImportActiveLoansFromDNA(string newerThanDateTime, bool skipExport = false);
|
||||
|
||||
Task<Import> ImportClosedLoansFromDNA(string newerThanDateTime);
|
||||
|
||||
Task<Import> ImportClosedLoansWithUpdatesFromDNA(string newerThanDateTime, bool skipExport);
|
||||
|
||||
Task<Import> ProcessActiveLoanImport(List<IndirectLoanDetail> loanDetails, DateTime currentTime, bool skipExport);
|
||||
|
||||
Task<Import> ProcessClosedLoanImport(List<ClosedIndirectLoan> closedLoans, DateTime currentTime, string newerThanDateTime);
|
||||
|
||||
Task<Import> ProcessClosedLoansWithUpdates(List<IndirectLoanDetail> dnaLoans, DateTime currentTime, bool skipExport);
|
||||
|
||||
Task<Import> ReadNewLoanImportFile(StreamReader importFile, string fileName, DateTime currentTime);
|
||||
|
||||
Task<List<ImportBoatLoan>> ReadBoatLoanImportFile(StreamReader importFile);
|
||||
|
||||
Task<Import> ReadClosedLoanImportFile(StreamReader importFile, string fileName, DateTime currentTime);
|
||||
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using ReserveManagementSystemLib.Enums;
|
||||
using ReserveManagementSystemLib.Models;
|
||||
|
||||
namespace ReserveManagementSystemApi.Services
|
||||
{
|
||||
public interface IImportService
|
||||
{
|
||||
Task<IEnumerable<Import>> GetAllAsync();
|
||||
|
||||
Task<IEnumerable<Import>> GetRecentAsync(int numberOfImports);
|
||||
|
||||
Task<Import> GetByIdAsync(int id);
|
||||
|
||||
Task<Import> AddImportAsync(Import import);
|
||||
|
||||
Task<Import> UpdateImportAsync(Import import);
|
||||
|
||||
Task<Import> GetLastLoanImportByType(ImportType importType);
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
using ReserveManagementSystemLib.Models;
|
||||
|
||||
namespace ReserveManagementSystemApi.Services
|
||||
{
|
||||
public interface ILoanCalculationService
|
||||
{
|
||||
LoanReserve CalculateSingleReserve(Loan loan);
|
||||
|
||||
LoanChargeback CalculateSingleChargeback(Loan loan);
|
||||
|
||||
Loan CalculateLoanReserve(Loan loan, decimal? originalReserve = null, DateTime? originalExportedReserveDate = null);
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
using ReserveManagementSystemLib.Models;
|
||||
|
||||
namespace ReserveManagementSystemApi.Services
|
||||
{
|
||||
public interface ILoanService
|
||||
{
|
||||
Task<IEnumerable<Loan>> GetAllAsync();
|
||||
|
||||
Task<PaginatedListResponse> GetFilteredAsync(LoanFilter filter);
|
||||
|
||||
Task<Loan> GetByIdAsync(int id);
|
||||
|
||||
Task<LoanReserve> GetLoanReserveAsync(LoanReserveInput input);
|
||||
|
||||
Task<LoanChargeback> GetLoanChargebackAsync(LoanChargebackInput input);
|
||||
|
||||
Task<List<Loan>> SaveLoansAsync(List<Loan> loans);
|
||||
|
||||
Task<List<Loan>> SaveWithChargebackAsync(List<Loan> loans);
|
||||
|
||||
Task<List<Loan>> SaveWithReserveAndChargebackAsync(List<Loan> loans);
|
||||
|
||||
Task<List<string>> SearchMembers(string searchText);
|
||||
|
||||
Task<List<string>> SearchLoanNumbers(string searchText);
|
||||
|
||||
Task<List<string>> SearchApplicationIds(string searchText);
|
||||
|
||||
Task UpdateDealerOriginalBalanceAsync(List<string> accountNumbers);
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
using ReserveManagementSystemLib.Models;
|
||||
|
||||
namespace ReserveManagementSystemApi.Services
|
||||
{
|
||||
public interface IRmsActiveLoanService
|
||||
{
|
||||
Task HandleRmsActiveImportRequest(RmsActiveImportRequest request);
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
namespace ReserveManagementSystemApi.Services
|
||||
{
|
||||
interface IScheduleService
|
||||
{
|
||||
Task RunImportExportActive(CancellationToken cancellationToken);
|
||||
Task RunImportExportClosed(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
using LCU.Library.Internal.StatusModels.Models;
|
||||
namespace ReserveManagementSystemApi.Services;
|
||||
|
||||
public interface IStatusService
|
||||
{
|
||||
EnvironmentView GetEnviromentView();
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
using ReserveManagementSystemLib.Models;
|
||||
|
||||
namespace ReserveManagementSystemApi.Services
|
||||
{
|
||||
public interface IVehicleService
|
||||
{
|
||||
|
||||
Task<Vehicle> GetVehicleByLoanIdAsync(int? loanId);
|
||||
}
|
||||
}
|
||||
+402
@@ -0,0 +1,402 @@
|
||||
using CsvHelper;
|
||||
using CsvHelper.Configuration;
|
||||
using System.Globalization;
|
||||
using ReserveManagementSystemApi.Helpers;
|
||||
using ReserveManagementSystemApi.Models;
|
||||
using ReserveManagementSystemApi.ServiceClients;
|
||||
using ReserveManagementSystemLib.Enums;
|
||||
using ReserveManagementSystemLib.Helpers;
|
||||
using ReserveManagementSystemLib.Models;
|
||||
using ReserveManagementSystemLib.Configuration;
|
||||
using ReserveManagementSystemApi.Enums;
|
||||
using LCU.Api.Internal.MaidBotLib.Models;
|
||||
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.IndirectLoan;
|
||||
|
||||
namespace ReserveManagementSystemApi.Services
|
||||
{
|
||||
public class ImportReadService : IImportReadService
|
||||
{
|
||||
private readonly ILoanService _loanService;
|
||||
private readonly IImportService _importService;
|
||||
private readonly IDealerService _dealerService;
|
||||
private readonly IExportService _exportService;
|
||||
private readonly ICoreApiServiceClient _coreApiServiceClient;
|
||||
private readonly RmsApiConfiguration _rmsApiConfig;
|
||||
private ImportHelper _importHelper = new ImportHelper();
|
||||
|
||||
public ImportReadService(ILoanService loanService,
|
||||
IImportService importService,
|
||||
IDealerService dealerService,
|
||||
IExportService exportService,
|
||||
ICoreApiServiceClient coreApiServiceClient,
|
||||
RmsApiConfiguration rmsApiConfig)
|
||||
{
|
||||
_loanService = loanService;
|
||||
_importService = importService;
|
||||
_dealerService = dealerService;
|
||||
_exportService = exportService;
|
||||
_coreApiServiceClient = coreApiServiceClient;
|
||||
_rmsApiConfig = rmsApiConfig;
|
||||
}
|
||||
|
||||
public async Task<Import> ImportActiveLoansFromDNA(string newerThanDateTime, bool skipExport = false)
|
||||
{
|
||||
if (string.IsNullOrEmpty(newerThanDateTime))
|
||||
{
|
||||
Import lastImport = await _importService.GetLastLoanImportByType(ImportType.Active);
|
||||
newerThanDateTime = GetStartDateForNextImport(lastImport);
|
||||
}
|
||||
Log.Information("Get Active Loans By Posted Date is starting.");
|
||||
var dnaLoans = await _coreApiServiceClient.GetIndirectLoansByPostedDate(newerThanDateTime);
|
||||
Log.Information($"Get Active Loans By Posted Date has ended. Found {dnaLoans.Count()} active loan records.");
|
||||
|
||||
Log.Information("Starting ProcessActiveLoanImport");
|
||||
var import = await ProcessActiveLoanImport(dnaLoans, DateTime.Now, skipExport);
|
||||
Log.Information("ProcessActiveLoanImport has ended");
|
||||
|
||||
return import;
|
||||
}
|
||||
|
||||
public async Task<Import> ImportClosedLoansFromDNA(string newerThanDateTime = "")
|
||||
{
|
||||
if (string.IsNullOrEmpty(newerThanDateTime))
|
||||
{
|
||||
var lastImport = await _importService.GetLastLoanImportByType(ImportType.Closed);
|
||||
newerThanDateTime = GetStartDateForNextImport(lastImport);
|
||||
}
|
||||
var dnaLoans = await _coreApiServiceClient.GetClosedLoansByPostedDate(newerThanDateTime);
|
||||
var import = await ProcessClosedLoanImport(dnaLoans, DateTime.Now, newerThanDateTime);
|
||||
|
||||
return import;
|
||||
}
|
||||
|
||||
public async Task<Import> ImportClosedLoansWithUpdatesFromDNA(string newerThanDateTime, bool skipExport)
|
||||
{
|
||||
var dnaLoans = await _coreApiServiceClient.GetClosedLoansWithDetailByClosedDate(newerThanDateTime);
|
||||
var newImport = new Import();
|
||||
|
||||
try
|
||||
{
|
||||
newImport = await ProcessClosedLoansWithUpdates(dnaLoans, DateTime.Now, skipExport);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure to import closed loans from DNA.");
|
||||
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
|
||||
Enum.GetName(typeof(GroupEnum), GroupEnum.IndirectLending))
|
||||
.Error(ex, $"Reserve Management System Failure. " +
|
||||
$"Error while importing Closed Loans from DNA.");
|
||||
throw;
|
||||
}
|
||||
|
||||
return newImport;
|
||||
}
|
||||
|
||||
public async Task<Import> ProcessActiveLoanImport(List<IndirectLoanDetail> loanDetails, DateTime currentTime, bool skipExport = false)
|
||||
{
|
||||
var newImport = new Import
|
||||
{
|
||||
ImportType = ImportType.Active.ToString(),
|
||||
StartDate = currentTime
|
||||
};
|
||||
|
||||
newImport = await _importService.AddImportAsync(newImport);
|
||||
|
||||
try
|
||||
{
|
||||
var loans = _importHelper.TransformLoanFromDNA(loanDetails, newImport.ImportId); //Transform import to loan object
|
||||
|
||||
await _dealerService.UpdateDealersAsync(loans);
|
||||
|
||||
var allDealers = await _dealerService.GetAllDealersAsync();
|
||||
loans.ForEach(loan =>
|
||||
{
|
||||
var currentDealer = allDealers.FirstOrDefault(a => a.DealerOrganizationNumber == loan.DealerOrganizationNumber);
|
||||
loan.Dealer = currentDealer; // assign the loan the updated dealer then check validation in save method
|
||||
});
|
||||
|
||||
await SaveLoansExportAndImportAsync(newImport, loans, currentTime, skipExport);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure to import active loans from DNA.");
|
||||
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
|
||||
Enum.GetName(typeof(GroupEnum), GroupEnum.IndirectLending))
|
||||
.Error(ex, $"Reserve Management System Failure. " +
|
||||
$"Error while importing Active Loans from DNA.");
|
||||
throw;
|
||||
}
|
||||
|
||||
return newImport;
|
||||
}
|
||||
|
||||
public async Task<Import> ProcessClosedLoansWithUpdates(List<IndirectLoanDetail> dnaLoans, DateTime currentTime, bool skipExport)
|
||||
{
|
||||
var closedImport = new Import
|
||||
{
|
||||
ImportType = ImportType.Closed.ToString(),
|
||||
StartDate = currentTime
|
||||
};
|
||||
|
||||
closedImport = await _importService.AddImportAsync(closedImport);
|
||||
var loans = _importHelper.TransformLoanFromDNA(dnaLoans, closedImport.ImportId);
|
||||
|
||||
await _dealerService.UpdateDealersAsync(loans);
|
||||
|
||||
var loansClosed = await _loanService.SaveWithReserveAndChargebackAsync(loans);
|
||||
|
||||
if (!skipExport)
|
||||
{
|
||||
//prevent converted loans from exporting
|
||||
DateTime defaultConvertedPostDate = Convert.ToDateTime(_rmsApiConfig.DefaultConvertedLoanPostDate);
|
||||
var convertedLoansMarkExported = loansClosed.Where(loan => loan.PostedDate < defaultConvertedPostDate && loan.LoanStatus == "Closed").ToList();
|
||||
await _exportService.MarkReservesAsExportedAsync(convertedLoansMarkExported, currentTime);
|
||||
|
||||
//update dealer original balance with only non-converted loans
|
||||
await _exportService.ExportReservesAsync(DateTime.Now);
|
||||
var nonConvertedLoans = loansClosed.Where(loan => loan.PostedDate > defaultConvertedPostDate).ToList();
|
||||
var listAccountNumbers = nonConvertedLoans.Select(loan => loan.LoanNumber).ToList();
|
||||
await _loanService.UpdateDealerOriginalBalanceAsync(listAccountNumbers);
|
||||
|
||||
await _exportService.ExportChargebacksAsync(DateTime.Now);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _exportService.MarkReservesAndChargebacksAsExportedAsync(loansClosed, DateTime.Now);
|
||||
}
|
||||
|
||||
closedImport.EndDate = DateTime.Now;
|
||||
closedImport.RecordCount = loansClosed.Count;
|
||||
closedImport.RecordsSaved = loansClosed.Count;
|
||||
closedImport.Success = true;
|
||||
|
||||
await _importService.UpdateImportAsync(closedImport);
|
||||
|
||||
return closedImport;
|
||||
}
|
||||
|
||||
public async Task<Import> ProcessClosedLoanImport(List<ClosedIndirectLoan> closedLoans, DateTime currentTime, string newerThanDateTime)
|
||||
{
|
||||
List<Loan> loansUpdated;
|
||||
var closedImport = new Import
|
||||
{
|
||||
ImportType = ImportType.Closed.ToString(),
|
||||
StartDate = currentTime
|
||||
};
|
||||
|
||||
closedImport = await _importService.AddImportAsync(closedImport);
|
||||
|
||||
try
|
||||
{
|
||||
loansUpdated = await CloseLoans(closedLoans, closedImport);
|
||||
var needDetails = loansUpdated.Where(l => l.LoanStatus == LoanStatus.MissingActiveLoan.ToString()).ToList();
|
||||
if (needDetails.Count > 0)
|
||||
{
|
||||
await ImportClosedLoansWithUpdatesFromDNA(newerThanDateTime, false);
|
||||
}
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure to import closed loans from DNA.");
|
||||
throw;
|
||||
}
|
||||
|
||||
return closedImport;
|
||||
}
|
||||
|
||||
public async Task<Import> ReadNewLoanImportFile(StreamReader importFile, string fileName, DateTime currentTime)
|
||||
{
|
||||
var loansFromImport = new List<ImportNewLoan>();
|
||||
var newImport = new Import
|
||||
{
|
||||
FileName = fileName.Truncate(50),
|
||||
StartDate = currentTime,
|
||||
ImportType = ImportType.Active.ToString()
|
||||
};
|
||||
|
||||
newImport = await _importService.AddImportAsync(newImport);
|
||||
|
||||
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
|
||||
{
|
||||
HasHeaderRecord = false,
|
||||
TrimOptions = TrimOptions.Trim,
|
||||
HeaderValidated = null,
|
||||
MissingFieldFound = null
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
using (var csv = new CsvReader(importFile, config))
|
||||
{
|
||||
loansFromImport = csv.GetRecords<ImportNewLoan>().ToList();
|
||||
}
|
||||
|
||||
var dealers = loansFromImport.Select(l => new Dealer
|
||||
{
|
||||
DealerOrganizationNumber = l.DealerOrganizationNumber
|
||||
})
|
||||
.GroupBy(d => d.DealerOrganizationNumber).First().ToList();
|
||||
|
||||
await _dealerService.UpdateDealersAsync(dealers);
|
||||
|
||||
var loans = _importHelper.TransformNewLoan(loansFromImport, newImport.ImportId); //Transform import to loan object
|
||||
|
||||
await SaveLoansExportAndImportAsync(newImport, loans, currentTime);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("Failure to read new loan file, message: {0} {1}", ex.Message, ex.StackTrace);
|
||||
throw;
|
||||
}
|
||||
|
||||
return newImport;
|
||||
}
|
||||
|
||||
public async Task<List<ImportBoatLoan>> ReadBoatLoanImportFile(StreamReader importFile)
|
||||
{
|
||||
var loansFromImport = new List<ImportBoatLoan>();
|
||||
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
|
||||
{
|
||||
HasHeaderRecord = false,
|
||||
TrimOptions = TrimOptions.Trim,
|
||||
HeaderValidated = null,
|
||||
MissingFieldFound = null,
|
||||
BadDataFound = null
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
using (var csv = new CsvReader(importFile, config))
|
||||
{
|
||||
loansFromImport = csv.GetRecords<ImportBoatLoan>().ToList();
|
||||
}
|
||||
|
||||
var loans = _importHelper.TransformBoatLoan(loansFromImport);
|
||||
loansFromImport = await ExportBoatLoansAsync(loans);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure to read boat loan file");
|
||||
throw;
|
||||
}
|
||||
|
||||
return loansFromImport;
|
||||
}
|
||||
|
||||
public async Task<Import> ReadClosedLoanImportFile(StreamReader importFile, string fileName, DateTime currentTime)
|
||||
{
|
||||
var loansFromImport = new List<ClosedIndirectLoan>();
|
||||
List<Loan> loansUpdated;
|
||||
|
||||
var closedImport = new Import
|
||||
{
|
||||
FileName = fileName.Truncate(50),
|
||||
StartDate = currentTime,
|
||||
ImportType = ImportType.Closed.ToString()
|
||||
};
|
||||
|
||||
closedImport = await _importService.AddImportAsync(closedImport);
|
||||
|
||||
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
|
||||
{
|
||||
HasHeaderRecord = true,
|
||||
TrimOptions = TrimOptions.Trim,
|
||||
PrepareHeaderForMatch = args => args.Header.ToLower()
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
using (var csv = new CsvReader(importFile, config))
|
||||
{
|
||||
loansFromImport = csv.GetRecords<ClosedIndirectLoan>().ToList();
|
||||
}
|
||||
|
||||
loansUpdated = await CloseLoans(loansFromImport, closedImport);
|
||||
Log.Information($"Closed {loansUpdated.Count} loans");
|
||||
await _exportService.ExportChargebacksAsync(DateTime.Now);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error("Failure to read closed loan file, message: {0} {1}", ex.Message, ex.StackTrace);
|
||||
throw;
|
||||
}
|
||||
|
||||
return closedImport;
|
||||
}
|
||||
|
||||
private async Task<List<Loan>> CloseLoans(List<ClosedIndirectLoan> loansFromImport, Import closedImport)
|
||||
{
|
||||
var loans = _importHelper.TransformClosedLoans(loansFromImport, closedImport.ImportId).ToList();
|
||||
var loansUpdated = await _loanService.SaveWithChargebackAsync(loans);
|
||||
|
||||
var missingActiveLoans = loansUpdated.Where(l => l.LoanStatus == LoanStatus.MissingActiveLoan.ToString()).ToList();
|
||||
|
||||
await _exportService.ExportChargebacksAsync(DateTime.Now);
|
||||
|
||||
closedImport.EndDate = DateTime.Now;
|
||||
closedImport.RecordCount = loansFromImport.Count;
|
||||
closedImport.RecordsSaved = loansUpdated.Count - missingActiveLoans.Count;
|
||||
closedImport.Success = true;
|
||||
|
||||
await _importService.UpdateImportAsync(closedImport);
|
||||
Log.Information($"Closed {loansUpdated.Count} loans");
|
||||
|
||||
return loansUpdated;
|
||||
}
|
||||
|
||||
private async Task<List<ImportBoatLoan>> ExportBoatLoansAsync(List<Loan> loans, bool skipExport = false)
|
||||
{
|
||||
var boatResults = new List<ImportBoatLoan>();
|
||||
if (!skipExport)
|
||||
{
|
||||
boatResults = await _exportService.ExportBoatLoansAsync(loans);
|
||||
var reserveLoans = loans.Where(l => l.ReserveAmount != 0);
|
||||
var listAccounts = reserveLoans.Select(loan => loan.LoanNumber).ToList();
|
||||
await _loanService.UpdateDealerOriginalBalanceAsync(listAccounts);
|
||||
}
|
||||
|
||||
Log.Information($"Imported {loans.Count} boat loans");
|
||||
return boatResults;
|
||||
}
|
||||
|
||||
private string GetStartDateForNextImport(Import lastImport)
|
||||
{
|
||||
if (lastImport?.StartDate != null)
|
||||
{
|
||||
return lastImport.StartDate.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
|
||||
}
|
||||
return _rmsApiConfig.DefaultImportStartDate;
|
||||
}
|
||||
|
||||
private async Task SaveLoansExportAndImportAsync(Import import, List<Loan> loans, DateTime currentTime, bool skipExport = false)
|
||||
{
|
||||
var loansImported = await _loanService.SaveLoansAsync(loans);
|
||||
if (!skipExport)
|
||||
{
|
||||
//prevent converted loans from exporting
|
||||
DateTime defaultConvertedPostDate = Convert.ToDateTime(_rmsApiConfig.DefaultConvertedLoanPostDate);
|
||||
var convertedLoansMarkExported = loansImported.Where(loan => loan.PostedDate < defaultConvertedPostDate && loan.LoanStatus == "New").ToList();
|
||||
await _exportService.MarkReservesAsExportedAsync(convertedLoansMarkExported, currentTime);
|
||||
|
||||
//update dealer original balance with only non-converted loans
|
||||
await _exportService.ExportReservesAsync(currentTime);
|
||||
var nonConvertedLoans = loansImported.Where(loan => loan.PostedDate > defaultConvertedPostDate).ToList();
|
||||
var listAccountNumbers = nonConvertedLoans.Select(loan => loan.LoanNumber).ToList();
|
||||
await _loanService.UpdateDealerOriginalBalanceAsync(listAccountNumbers);
|
||||
}
|
||||
else
|
||||
{
|
||||
await _exportService.MarkReservesAsExportedAsync(loansImported, currentTime);
|
||||
}
|
||||
|
||||
Log.Information($"Imported {loansImported.Count} active loans");
|
||||
import.EndDate = DateTime.Now;
|
||||
import.RecordCount = loansImported.Count;
|
||||
import.RecordsSaved = loansImported.Count;
|
||||
import.Success = true;
|
||||
|
||||
await _importService.UpdateImportAsync(import);
|
||||
}
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
using ReserveManagementSystemLib.Enums;
|
||||
using ReserveManagementSystemLib.Models;
|
||||
using ReserveManagementSystemLib.Repositories;
|
||||
|
||||
namespace ReserveManagementSystemApi.Services
|
||||
{
|
||||
public class ImportService: IImportService
|
||||
{
|
||||
private readonly IImportRepository _importRepository;
|
||||
|
||||
public ImportService(IImportRepository importRepository)
|
||||
{
|
||||
_importRepository = importRepository;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Import>> GetAllAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _importRepository.GetAll().OrderByDescending(imp => imp.StartDate).ToListAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving imports.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Import> GetLastLoanImportByType(ImportType importType)
|
||||
{
|
||||
Import import;
|
||||
try
|
||||
{
|
||||
import = await _importRepository.GetLastLoanDnaImportByType(importType).FirstOrDefaultAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving last import.");
|
||||
throw;
|
||||
}
|
||||
|
||||
return import;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Import>> GetRecentAsync(int numberOfImports)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _importRepository.GetAll().OrderByDescending(imp => imp.StartDate).Take(numberOfImports).ToListAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving imports.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Import> GetByIdAsync(int id)
|
||||
{
|
||||
Import import;
|
||||
try
|
||||
{
|
||||
import = await _importRepository.FindByIdAsync(id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving import with id {id}.");
|
||||
throw;
|
||||
}
|
||||
|
||||
if (import == null)
|
||||
{
|
||||
throw new Exception($"Import with id {id} not found.");
|
||||
}
|
||||
|
||||
return import;
|
||||
}
|
||||
|
||||
public async Task<Import> AddImportAsync(Import newImport)
|
||||
{
|
||||
return await _importRepository.AddImportAsync(newImport);
|
||||
}
|
||||
|
||||
public async Task<Import> UpdateImportAsync(Import import)
|
||||
{
|
||||
return await _importRepository.UpdateImportAsync(import);
|
||||
}
|
||||
}
|
||||
}
|
||||
+314
@@ -0,0 +1,314 @@
|
||||
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)
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
+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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
using ReserveManagementSystemLib.Models;
|
||||
|
||||
namespace ReserveManagementSystemApi.Services.QueueServices
|
||||
{
|
||||
public interface IQueueRmsActiveLoanService
|
||||
{
|
||||
Task QueueRmsActiveLoanServiceRequest(RmsActiveImportRequest request);
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
using LCU.Library.Internal.LcuMassTransitModels;
|
||||
using MassTransit;
|
||||
using Newtonsoft.Json;
|
||||
using ReserveManagementSystemApi.Configurations;
|
||||
using ReserveManagementSystemLib.Models;
|
||||
|
||||
namespace ReserveManagementSystemApi.Services.QueueServices
|
||||
{
|
||||
public class QueueRmsActiveLoanService : IQueueRmsActiveLoanService
|
||||
{
|
||||
private readonly ISendEndpoint _sendEndpoint;
|
||||
private readonly ReceiveEndpointConfiguration _rmsActiveLoanImportReceiveEndpoint;
|
||||
|
||||
public QueueRmsActiveLoanService(IBusControl busControl, ReceiveEndpointConfigurations receiveEndpointConfigurations)
|
||||
{
|
||||
_rmsActiveLoanImportReceiveEndpoint = receiveEndpointConfigurations.RmsActiveLoanReceiveEndpoint;
|
||||
_sendEndpoint = busControl.GetSendEndpoint(_rmsActiveLoanImportReceiveEndpoint.ReceiveEndpointUri).Result;
|
||||
}
|
||||
|
||||
public async Task QueueRmsActiveLoanServiceRequest(RmsActiveImportRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cancellationTokenSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
|
||||
await _sendEndpoint.Send(request, cancellationTokenSource.Token);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
var message = "Failed to Queue RmsActiveLoanImport Request. " +
|
||||
$"RmsActiveLoanImport: {JsonConvert.SerializeObject(request)}";
|
||||
Log.Error(exception, message);
|
||||
throw new ServiceException($"{message}, Exception Message: {exception.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
using ReserveManagementSystemApi.Services.QueueServices;
|
||||
using ReserveManagementSystemLib.Models;
|
||||
|
||||
namespace ReserveManagementSystemApi.Services
|
||||
{
|
||||
public class RmsActiveLoanService : IRmsActiveLoanService
|
||||
{
|
||||
private readonly IQueueRmsActiveLoanService _queueRmsActiveLoanService;
|
||||
private readonly IImportReadService _importReadService;
|
||||
|
||||
public RmsActiveLoanService(
|
||||
IQueueRmsActiveLoanService queueRmsActiveLoanService, IImportReadService importReadService
|
||||
)
|
||||
{
|
||||
_queueRmsActiveLoanService = queueRmsActiveLoanService;
|
||||
_importReadService = importReadService;
|
||||
}
|
||||
public async Task HandleRmsActiveImportRequest(RmsActiveImportRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _importReadService.ImportActiveLoansFromDNA(request.ImportDateTime, request.SkipExport);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure importing Active Loans from DNA");
|
||||
throw new ServiceException($"Exception Message: {ex.Message} - Failure importing Active Loans from DNA");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
namespace ReserveManagementSystemApi.Services
|
||||
{
|
||||
public class ScheduleService : IScheduleService
|
||||
{
|
||||
private readonly IImportReadService _importReadService;
|
||||
private readonly IExportService _exportService;
|
||||
|
||||
public ScheduleService(IImportReadService importReadService, IExportService exportService)
|
||||
{
|
||||
_importReadService = importReadService;
|
||||
_exportService = exportService;
|
||||
}
|
||||
|
||||
public async Task RunImportExportActive(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Information("Importing active loans from DNA");
|
||||
var activeImport = await _importReadService.ImportActiveLoansFromDNA(string.Empty);
|
||||
Log.Information($"{activeImport.RecordsSaved} active loans were saved");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failed to perform scheduled active loan import.");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task RunImportExportClosed(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Information("Importing closed loans from DNA");
|
||||
var closedImport = await _importReadService.ImportClosedLoansFromDNA(string.Empty);
|
||||
Log.Information($"{closedImport.RecordsSaved} loans were closed");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failed to perform scheduled closed loan import.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
using LCU.Library.Internal.Status.Services;
|
||||
using LCU.Library.Internal.StatusModels.Models;
|
||||
namespace ReserveManagementSystemApi.Services;
|
||||
|
||||
public class StatusService : IStatusService
|
||||
{
|
||||
private readonly IEnvironmentViewService _environmentViewService;
|
||||
|
||||
public StatusService(IEnvironmentViewService environmentViewService)
|
||||
{
|
||||
_environmentViewService = environmentViewService;
|
||||
}
|
||||
|
||||
public EnvironmentView GetEnviromentView()
|
||||
{
|
||||
try
|
||||
{
|
||||
var environmentView = _environmentViewService.BuildEnvironmentView();
|
||||
Log.Verbose("Status Environment View Heartbeat. EnvironmentView: {@EnvironmentView}", environmentView);
|
||||
return environmentView;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, $"Failure Getting Enviroment View Information.");
|
||||
throw new ServiceException($"Failure Getting Enviroment View Information.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
using ReserveManagementSystemLib.Models;
|
||||
using ReserveManagementSystemLib.Repositories;
|
||||
|
||||
namespace ReserveManagementSystemApi.Services
|
||||
{
|
||||
public class VehicleService: IVehicleService
|
||||
{
|
||||
private readonly IVehicleRepository _vehicleRepository;
|
||||
|
||||
public VehicleService(IVehicleRepository vehicleRepository)
|
||||
{
|
||||
_vehicleRepository = vehicleRepository;
|
||||
}
|
||||
|
||||
|
||||
public async Task<Vehicle> GetVehicleByLoanIdAsync(int? loanId)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _vehicleRepository.FindByLoanIdAsync(loanId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving vehicle by loan id.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user