Adding
This commit is contained in:
+11
@@ -0,0 +1,11 @@
|
||||
namespace ReserveManagementSystemApi.Configurations
|
||||
{
|
||||
public class ApiConfiguration
|
||||
{
|
||||
public string AppName { get; set; }
|
||||
public string SeqUrl { get; set; }
|
||||
public string SeqApiKey { get; set; }
|
||||
public string[] CorsAllowedOrigins { get; set; }
|
||||
public FeatureFlags FeatureFlags { get; set; }
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
namespace ReserveManagementSystemApi.Configurations
|
||||
{
|
||||
public class FeatureFlags
|
||||
{
|
||||
public bool RunRmsActiveLoanConsumer { get; set; }
|
||||
public bool EnableImportExportActive { get; set; }
|
||||
public bool EnableImportExportClosed { get; set; }
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
using LCU.Library.Internal.LcuMassTransitModels;
|
||||
|
||||
namespace ReserveManagementSystemApi.Configurations
|
||||
{
|
||||
public class ReceiveEndpointConfigurations
|
||||
{
|
||||
public ReceiveEndpointConfiguration RmsActiveLoanReceiveEndpoint { get; set; }
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
namespace ReserveManagementSystemApi.Configurations
|
||||
{
|
||||
public class SqlConnectionConfiguration
|
||||
{
|
||||
public string ConnectionString { get; set; }
|
||||
public int MaxRetryCount { get; set; }
|
||||
public TimeSpan MaxRetryDelay { get; set; }
|
||||
}
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
using MassTransit;
|
||||
using LCU.Api.Internal.MaidBotLib.Models;
|
||||
using ReserveManagementSystemLib.Models;
|
||||
using ReserveManagementSystemApi.Services;
|
||||
|
||||
namespace ReserveManagementSystemApi.Consumers;
|
||||
|
||||
public class RmsActiveImportConsumer : IConsumer<RmsActiveImportRequest>
|
||||
{
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
|
||||
public RmsActiveImportConsumer(IServiceScopeFactory scopeFactory)
|
||||
{
|
||||
_scopeFactory = scopeFactory;
|
||||
}
|
||||
|
||||
public async Task Consume(ConsumeContext<RmsActiveImportRequest> context)
|
||||
{
|
||||
try
|
||||
{
|
||||
Log.Debug("Starting RmsActiveImportConsumer.");
|
||||
using var scope = _scopeFactory.CreateScope();
|
||||
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IRmsActiveLoanService>().HandleRmsActiveImportRequest(context.Message));
|
||||
Log.Debug("Ending RmsActiveImportConsumer.");
|
||||
}
|
||||
catch (ServiceException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
|
||||
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
|
||||
.Fatal(exception, $"Unexpected RmsActiveImportConsumer Failure. " +
|
||||
$"The RmsActiveImportRequest is placed on ReserveManagementSystemApi.ActiveImport_error.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
using ReserveManagementSystemApi.Services;
|
||||
using ReserveManagementSystemLib.Models;
|
||||
|
||||
namespace ReserveManagementSystemApi.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class AuditHistoryController : ControllerBase
|
||||
{
|
||||
private readonly IAuditHistoryService _auditHistoryService;
|
||||
|
||||
public AuditHistoryController(IAuditHistoryService auditHistoryService)
|
||||
{
|
||||
_auditHistoryService = auditHistoryService;
|
||||
}
|
||||
|
||||
[HttpGet(Name = "GetAllHistory")]
|
||||
[ProducesResponseType(typeof(IEnumerable<AuditHistory>), 200)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> Get()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _auditHistoryService.GetAllAsync();
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Failure returning Audit Histories");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
using ReserveManagementSystemApi.Services;
|
||||
using ReserveManagementSystemLib.Models;
|
||||
|
||||
namespace ReserveManagementSystemApi.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class DealerController : ControllerBase
|
||||
{
|
||||
private readonly IDealerService _dealerService;
|
||||
|
||||
public DealerController(IDealerService dealerService)
|
||||
{
|
||||
_dealerService = dealerService;
|
||||
}
|
||||
|
||||
[HttpGet("Search/{dealerSearchText}", Name = "Search")]
|
||||
[ProducesResponseType(typeof(IEnumerable<Dealer>), 200)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> Search(string dealerSearchText)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _dealerService.Search(dealerSearchText);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Failure returning dealers");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("GetByDealerCode/{dealerCode}", Name = "GetDealerByCode")]
|
||||
[ProducesResponseType(typeof(Dealer), 200)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> GetByDealerCode(string dealerCode)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _dealerService.GetDealerByCodeAsync(dealerCode);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Failure returning dealer");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+183
@@ -0,0 +1,183 @@
|
||||
using ReserveManagementSystemApi.Models;
|
||||
using ReserveManagementSystemApi.Services;
|
||||
using ReserveManagementSystemApi.Services.QueueServices;
|
||||
using ReserveManagementSystemLib.Models;
|
||||
|
||||
namespace ReserveManagementSystemApi.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class ImportController : ControllerBase
|
||||
{
|
||||
private readonly IImportReadService _importReadService;
|
||||
private readonly IImportService _importService;
|
||||
private readonly IQueueRmsActiveLoanService _queueRmsActiveLoanService;
|
||||
|
||||
public ImportController(IImportReadService importReadService, IImportService importService, IQueueRmsActiveLoanService queueRmsActiveLoanService)
|
||||
{
|
||||
_importReadService = importReadService;
|
||||
_importService = importService;
|
||||
_queueRmsActiveLoanService = queueRmsActiveLoanService;
|
||||
}
|
||||
|
||||
[HttpGet(Name = "GetAllImports")]
|
||||
[ProducesResponseType(typeof(IEnumerable<Import>), 200)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> Get()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _importService.GetAllAsync();
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Failure returning Imports");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("GetRecentImports/{numberOfImports}", Name = "GetRecentImports")]
|
||||
[ProducesResponseType(typeof(IEnumerable<Import>), 200)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> GetRecent(int numberOfImports)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _importService.GetRecentAsync(numberOfImports);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Failure returning Imports");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("GetById/{importId}", Name = "GetImportById")]
|
||||
[ProducesResponseType(typeof(Loan), 200)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> GetById(int importId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _importService.GetByIdAsync(importId);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"Failure returning loan with id {importId}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("ImportNewLoanFile", Name ="ImportNewLoanFile")]
|
||||
[ProducesResponseType(typeof(Import), 200)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> ImportNewLoanFile(IFormFile file)
|
||||
{
|
||||
try
|
||||
{
|
||||
var stream = new StreamReader(file.OpenReadStream());
|
||||
var import = await _importReadService.ReadNewLoanImportFile(stream, file.FileName, DateTime.Now);
|
||||
return Ok(import);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("Failure to import new loan file, message: {0} {1}", e.Message, e.StackTrace);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("ImportBoatLoanFile", Name = "ImportBoatLoanFile")]
|
||||
[ProducesResponseType(typeof(List<ImportBoatLoan>), 200)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> ImportBoatLoanFile(IFormFile file)
|
||||
{
|
||||
try
|
||||
{
|
||||
var stream = new StreamReader(file.OpenReadStream());
|
||||
var import = await _importReadService.ReadBoatLoanImportFile(stream);
|
||||
return Ok(import);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("Failure to import boat loan file, message: {0} {1}", e.Message, e.StackTrace);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("ImportClosedLoanFile", Name = "ImportClosedLoanFile")]
|
||||
[ProducesResponseType(typeof(Import), 200)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> ImportClosedLoanFile(IFormFile file)
|
||||
{
|
||||
try
|
||||
{
|
||||
var stream = new StreamReader(file.OpenReadStream());
|
||||
var import = await _importReadService.ReadClosedLoanImportFile(stream, file.FileName, DateTime.Now);
|
||||
return Ok(import);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("Failure to import closed loan file, message: {0} {1}", e.Message, e.StackTrace);
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("ImportActiveLoans", Name = "ImportActiveLoans")]
|
||||
[ProducesResponseType(typeof(Import), 200)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> ImportActiveLoans(string? newerThanDateTime = "", bool skipExport = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
var request = new RmsActiveImportRequest()
|
||||
{
|
||||
ImportDateTime = newerThanDateTime,
|
||||
SkipExport = skipExport
|
||||
};
|
||||
await _queueRmsActiveLoanService.QueueRmsActiveLoanServiceRequest(request);
|
||||
return Accepted();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure importing Active Loans");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("ImportClosedLoans", Name = "ImportClosedLoans")]
|
||||
[ProducesResponseType(typeof(Import), 200)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> ImportClosedLoans(string? newerThanDateTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
var import = await _importReadService.ImportClosedLoansFromDNA(newerThanDateTime);
|
||||
return Ok(import);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure importing Closed Loans");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPut("ImportClosedLoansWithUpdates", Name = "ImportClosedLoansWithUpdates")]
|
||||
[ProducesResponseType(typeof(Import), 200)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> ImportClosedLoansWithUpdates(string? newerThanDateTime, bool skipExport = false)
|
||||
{
|
||||
try
|
||||
{
|
||||
var import = await _importReadService.ImportClosedLoansWithUpdatesFromDNA(newerThanDateTime, skipExport);
|
||||
return Ok(import);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
using ReserveManagementSystemApi.Services;
|
||||
using ReserveManagementSystemLib.Models;
|
||||
|
||||
namespace ReserveManagementSystemApi.Controllers
|
||||
{
|
||||
[Route("api/[controller]")]
|
||||
[ApiController]
|
||||
public class LoanController : ControllerBase
|
||||
{
|
||||
private readonly ILoanService _loanService;
|
||||
public LoanController(ILoanService loanService)
|
||||
{
|
||||
_loanService = loanService;
|
||||
}
|
||||
|
||||
[HttpGet(Name = "GetAllLoans")]
|
||||
[ProducesResponseType(typeof(IEnumerable<Loan>), 200)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> Get()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _loanService.GetAllAsync();
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Failure returning Loans");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("SearchLoans", Name = "SearchLoans")]
|
||||
[ProducesResponseType(typeof(PaginatedListResponse), 200)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> Post([FromBody]LoanFilter filter)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _loanService.GetFilteredAsync(filter);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, "Failure returning Filtered Loans");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("SearchMembers/{searchText}", Name = "SearchMembers")]
|
||||
[ProducesResponseType(typeof(string), 200)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> SearchMembers(string searchText)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _loanService.SearchMembers(searchText);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"Failure returning members");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("SearchLoanNumbers/{searchText}", Name = "SearchLoanNumbers")]
|
||||
[ProducesResponseType(typeof(string), 200)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> SearchLoanNumbers(string searchText)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _loanService.SearchLoanNumbers(searchText);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"Failure returning loan numbers");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("SearchApplicationIds/{searchText}", Name = "SearchApplicationIds")]
|
||||
[ProducesResponseType(typeof(string), 200)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> SearchApplicationIds(string searchText)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _loanService.SearchApplicationIds(searchText);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"Failure returning application ids");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpGet("GetById/{loanId}", Name = "GetLoanById")]
|
||||
[ProducesResponseType(typeof(Loan), 200)]
|
||||
[ProducesResponseType(500)]
|
||||
public async Task<IActionResult> GetById(int loanId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = await _loanService.GetByIdAsync(loanId);
|
||||
return Ok(result);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error(e, $"Failure returning loan with id {loanId}");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("CalculateReserve", Name = "CalculateReserve")]
|
||||
public async Task<LoanReserve> Post([FromBody] LoanReserveInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _loanService.GetLoanReserveAsync(input);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("Failure to Calculate Loan Reserve: {0} {1}", e.Message, e.StackTrace);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
[HttpPost("CalculateChargeback", Name = "CalculateChargeback")]
|
||||
public async Task<LoanChargeback> Post([FromBody] LoanChargebackInput input)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _loanService.GetLoanChargebackAsync(input);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Log.Error("Failure to Calculate Loan Chargeback: {0} {1}", e.Message, e.StackTrace);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
using ReserveManagementSystemApi.Services;
|
||||
namespace ReserveManagementSystemApi.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class StatusController : ControllerBase
|
||||
{
|
||||
private readonly IStatusService _statusService;
|
||||
public StatusController(IStatusService statusService)
|
||||
{
|
||||
_statusService = statusService;
|
||||
}
|
||||
|
||||
[HttpGet]
|
||||
[ProducesResponseType(200)]
|
||||
[ProducesResponseType(500)]
|
||||
public IActionResult Get()
|
||||
{
|
||||
try
|
||||
{
|
||||
return Ok(_statusService.GetEnviromentView());
|
||||
}
|
||||
catch (ServiceException exception)
|
||||
{
|
||||
return StatusCode(StatusCodes.Status500InternalServerError, exception.Message);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error(exception, "Unexpected Exception");
|
||||
return StatusCode(StatusCodes.Status500InternalServerError);
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
namespace ReserveManagementSystemApi.Enums
|
||||
{
|
||||
public enum ActivityType
|
||||
{
|
||||
Warning,
|
||||
Error,
|
||||
LoanStatusChange,
|
||||
NewLoanUpdate
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
namespace ReserveManagementSystemApi.Enums
|
||||
{
|
||||
public enum LoanStatus
|
||||
{
|
||||
New,
|
||||
Updated,
|
||||
Closed,
|
||||
MissingActiveLoan
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
namespace ReserveManagementSystemApi.Helpers
|
||||
{
|
||||
public static class BatchHelper
|
||||
{
|
||||
public static IEnumerable<IEnumerable<TSource>> CreateBatch <TSource>(
|
||||
List<TSource> source, int batchSize)
|
||||
{
|
||||
var items = new TSource[batchSize];
|
||||
var count = 0;
|
||||
foreach (var item in source)
|
||||
{
|
||||
items[count++] = item;
|
||||
if (count == batchSize)
|
||||
{
|
||||
yield return items;
|
||||
items = new TSource[batchSize];
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (count > 0)
|
||||
{
|
||||
yield return items.Take(count);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
using ReserveManagementSystemLib.Models;
|
||||
|
||||
namespace ReserveManagementSystemApi.Helpers
|
||||
{
|
||||
public class BoatLoanCalculationHelper
|
||||
{
|
||||
private LoanReserve loanReserve = new LoanReserve();
|
||||
|
||||
public BoatLoanCalculationHelper()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public LoanReserve CalculateStandardMarkup(CalculationConfig cc)
|
||||
{
|
||||
var markup = cc.LoanData().ContractRate - cc.LoanData().BuyRate;
|
||||
loanReserve.ReserveTypeMessage = "Standard Reserve";
|
||||
switch (markup)
|
||||
{
|
||||
case decimal d when (d >= (decimal)0.00 && d <= (decimal)0.49):
|
||||
loanReserve.ReserveAmount = cc.LoanData().LoanAmount * (decimal).01;
|
||||
break;
|
||||
case decimal d when (d >= (decimal)0.50 && d <= (decimal)0.74):
|
||||
loanReserve.ReserveAmount = cc.LoanData().LoanAmount * (decimal).02;
|
||||
break;
|
||||
case decimal d when (d >= (decimal)0.75 && d <= (decimal)0.99):
|
||||
loanReserve.ReserveAmount = cc.LoanData().LoanAmount * (decimal).03;
|
||||
break;
|
||||
case decimal d when (d >= (decimal)1.00 && d <= (decimal)1.49):
|
||||
loanReserve.ReserveAmount = cc.LoanData().LoanAmount * (decimal).04;
|
||||
break;
|
||||
case decimal d when (d >= (decimal)1.50 && d <= (decimal)1.99):
|
||||
loanReserve.ReserveAmount = cc.LoanData().LoanAmount * (decimal).05;
|
||||
break;
|
||||
case decimal d when (d == (decimal)2.00):
|
||||
loanReserve.ReserveAmount = cc.LoanData().LoanAmount * (decimal).06;
|
||||
break;
|
||||
case decimal d when (d > (decimal)2.00):
|
||||
loanReserve.ReserveAmount = cc.LoanData().LoanAmount * (decimal).06;
|
||||
loanReserve.ReserveTypeMessage = "Standard Reserve - Exceeds Max Markup";
|
||||
break;
|
||||
default:
|
||||
loanReserve.ReserveAmount = 0;
|
||||
loanReserve.ReserveTypeMessage = "No Reserve";
|
||||
break;
|
||||
}
|
||||
return loanReserve;
|
||||
|
||||
}
|
||||
|
||||
internal DateTime GetLastChargebackDate (DateTime firstPmtDate, int numOfChargebackPayments)
|
||||
{
|
||||
return firstPmtDate.AddMonths(numOfChargebackPayments - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
+210
@@ -0,0 +1,210 @@
|
||||
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.IndirectLoan;
|
||||
using ReserveManagementSystemApi.Models;
|
||||
using ReserveManagementSystemLib.Models;
|
||||
|
||||
namespace ReserveManagementSystemApi.Helpers
|
||||
{
|
||||
public class ImportHelper
|
||||
{
|
||||
private string _firstName;
|
||||
private string _middleInitial;
|
||||
private const double MonthInDays = 30.4375;
|
||||
|
||||
public ImportHelper()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public List<Loan> TransformNewLoan(List<Models.ImportNewLoan> iImportLoans, int iImportId)
|
||||
{
|
||||
var result = new List<Loan>();
|
||||
|
||||
foreach (var iLoan in iImportLoans)
|
||||
{
|
||||
Loan loan = new Loan();
|
||||
loan.ImportId = iImportId;
|
||||
loan.MemberNumber = iLoan.MemberNumber;
|
||||
loan.LoanNumber = iLoan.LoanNumber;
|
||||
SplitFirstMiddleName(iLoan.FirstAndMiddleName);
|
||||
loan.MemberFirstName = _firstName?.ToUpper();
|
||||
loan.MemberInitial = _middleInitial?.ToUpper();
|
||||
|
||||
loan.MemberLastName = iLoan.LastName?.ToUpper();
|
||||
loan.DealerOrganizationNumber = iLoan.DealerOrganizationNumber;
|
||||
loan.LoanDate = iLoan.LoanDate;
|
||||
loan.PostedDate = iLoan.PostedDate;
|
||||
loan.FirstPmtDate = iLoan.FirstPmtDate;
|
||||
loan.FinalPmtDate = iLoan.FinalPmtDate;
|
||||
loan.LoanAmount = iLoan.LoanAmount;
|
||||
loan.BuyRate = Math.Round(iLoan.BuyRate, 3, MidpointRounding.AwayFromZero);
|
||||
loan.ContractRate = Math.Round(iLoan.ContractRate, 3, MidpointRounding.AwayFromZero);
|
||||
loan.MonthlyPmt = iLoan.MonthlyPmt;
|
||||
loan.LoanTypeId = iLoan.LoanType;
|
||||
loan.ChargeBack = null;
|
||||
loan.PaidLoanDate = null;
|
||||
loan.ApplicationId = iLoan.ApplicationId;
|
||||
loan.AccountType = iLoan.AccountType;
|
||||
loan.Gap = !string.IsNullOrEmpty(iLoan.Gap) && Convert.ToBoolean(iLoan.Gap);
|
||||
loan.Warranty = !string.IsNullOrEmpty(iLoan.Warranty) && Convert.ToBoolean(iLoan.Warranty);
|
||||
loan.CreditLife = !string.IsNullOrEmpty(iLoan.CreditLife) && Convert.ToBoolean(iLoan.CreditLife);
|
||||
loan.CreditDisability = !string.IsNullOrEmpty(iLoan.CreditDisability) && Convert.ToBoolean(iLoan.CreditDisability);
|
||||
loan.Mbp = !string.IsNullOrEmpty(iLoan.Mbp) && Convert.ToBoolean(iLoan.Mbp);
|
||||
loan.Term = (int)Math.Round(Convert.ToDecimal(iLoan.OriginalTermInDays) / (decimal)MonthInDays, 0);
|
||||
|
||||
Vehicle vehicle = new Vehicle();
|
||||
vehicle.VINNumber = !string.IsNullOrEmpty(iLoan.VINNumber) ? iLoan.VINNumber : null;
|
||||
vehicle.Make = iLoan.Make;
|
||||
vehicle.Model = iLoan.Model;
|
||||
vehicle.Year = iLoan.Year;
|
||||
vehicle.Mileage = ValidateMileage(iLoan.Mileage);
|
||||
vehicle.Color = iLoan.Color;
|
||||
loan.Vehicle = vehicle;
|
||||
result.Add(loan);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<Loan> TransformLoanFromDNA(List<IndirectLoanDetail> importLoans, int iImportId)
|
||||
{
|
||||
var result = new List<Loan>();
|
||||
if (!importLoans.Any())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var iLoan in importLoans)
|
||||
{
|
||||
Loan loan = new Loan
|
||||
{
|
||||
ImportId = iImportId,
|
||||
MemberNumber = iLoan.MemberNumber,
|
||||
LoanNumber = iLoan.LoanNumber,
|
||||
MemberFirstName = iLoan.MemberFirstName?.ToUpper(),
|
||||
MemberInitial = iLoan.MemberInitial?.ToUpper(),
|
||||
MemberLastName = iLoan.MemberLastName?.ToUpper(),
|
||||
DealerOrganizationNumber = (!String.IsNullOrWhiteSpace(iLoan.DealerOrganizationNumber)) ? iLoan.DealerOrganizationNumber : string.Empty,
|
||||
LoanDate = iLoan.LoanDate,
|
||||
FirstPmtDate = iLoan.FirstPmtDate,
|
||||
FinalPmtDate = iLoan.FinalPmtDate,
|
||||
LoanAmount = iLoan.LoanAmount,
|
||||
BuyRate = (iLoan.BuyRate != 0) ? Math.Round(iLoan.BuyRate, 3, MidpointRounding.AwayFromZero) : 0,
|
||||
ContractRate = Math.Round(iLoan.ContractRate, 3, MidpointRounding.AwayFromZero),
|
||||
MonthlyPmt = iLoan.MonthlyPmt,
|
||||
LoanTypeId = iLoan.LoanType,
|
||||
ApplicationId = iLoan.ApplicationId,
|
||||
Gap = iLoan.GAP,
|
||||
Warranty = iLoan.Warranty,
|
||||
CreditLife = iLoan.CreditLife,
|
||||
CreditDisability = iLoan.CreditDisability,
|
||||
Mbp = iLoan.Mbp,
|
||||
AccountType = iLoan.AccountType,
|
||||
PaidLoanDate = iLoan.PaidLoanDate,
|
||||
Term = (int)Math.Round(Convert.ToDecimal(iLoan.OriginalTermInDays) / (decimal)MonthInDays, 0)
|
||||
};
|
||||
|
||||
if (iLoan.PostedDate == DateTime.MinValue)
|
||||
{
|
||||
loan.PostedDate = iLoan.LoanDate;
|
||||
}
|
||||
else
|
||||
{
|
||||
loan.PostedDate = iLoan.PostedDate;
|
||||
}
|
||||
|
||||
loan.Vehicle = new Vehicle
|
||||
{
|
||||
VINNumber = !string.IsNullOrEmpty(iLoan.Vin) ? iLoan.Vin : null,
|
||||
Make = iLoan.Make,
|
||||
Model = iLoan.Model,
|
||||
Year = iLoan.Year.ToString(),
|
||||
Mileage = ValidateMileage(iLoan.Mileage.ToString()),
|
||||
Color = iLoan.Color
|
||||
};
|
||||
|
||||
loan.Dealer = new Dealer
|
||||
{
|
||||
DealerCode = (!String.IsNullOrWhiteSpace(iLoan.DealerCode)) && (iLoan.DealerCode.Length <= 4) ? iLoan.DealerCode : string.Empty,
|
||||
Name = (!String.IsNullOrWhiteSpace(iLoan.DealerName)) ? iLoan.DealerName : string.Empty,
|
||||
DealerAccountNumber = (!String.IsNullOrWhiteSpace(iLoan.DealerAccountNumber)) ? iLoan.DealerAccountNumber : string.Empty,
|
||||
DealerOrganizationNumber = (!String.IsNullOrWhiteSpace(iLoan.DealerOrganizationNumber)) ? iLoan.DealerOrganizationNumber : string.Empty
|
||||
};
|
||||
|
||||
result.Add(loan);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<Loan> TransformBoatLoan(List<ImportBoatLoan> importLoans)
|
||||
{
|
||||
var result = new List<Loan>();
|
||||
if (!importLoans.Any())
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
foreach (var iLoan in importLoans)
|
||||
{
|
||||
Loan loan = new Loan
|
||||
{
|
||||
LoanNumber = iLoan.LoanNumber,
|
||||
ReserveAmount = (!String.IsNullOrWhiteSpace(iLoan.IsChargeback) && iLoan.IsChargeback.ToUpper() == "FALSE") ? iLoan.Amount : 0,
|
||||
ChargeBack = (!String.IsNullOrWhiteSpace(iLoan.IsChargeback) && iLoan.IsChargeback.ToUpper() == "TRUE") ? iLoan.Amount : 0,
|
||||
Adjustment = (!String.IsNullOrWhiteSpace(iLoan.IsAdjustment) && iLoan.IsAdjustment.ToUpper() == "TRUE") ? iLoan.Amount : 0,
|
||||
AccountType = iLoan.AccountType
|
||||
};
|
||||
|
||||
loan.Dealer = new Dealer
|
||||
{
|
||||
DealerAccountNumber = iLoan.DealerAccountNumber
|
||||
};
|
||||
|
||||
result.Add(loan);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private double ValidateMileage(string mileage)
|
||||
{
|
||||
double convertedMileage;
|
||||
if (double.TryParse(mileage, out convertedMileage))
|
||||
{
|
||||
return convertedMileage;
|
||||
}
|
||||
return convertedMileage;
|
||||
}
|
||||
|
||||
public IEnumerable<Loan> TransformClosedLoans(List<ClosedIndirectLoan> importLoans, int importId)
|
||||
{
|
||||
return importLoans.Select(x => new Loan
|
||||
{
|
||||
ImportId = importId,
|
||||
LoanNumber = x.LoanNumber,
|
||||
PaidLoanDate = x.PaidLoanDate
|
||||
//will update LoanStatus when saving
|
||||
});
|
||||
}
|
||||
|
||||
private void SplitFirstMiddleName(string name)
|
||||
{
|
||||
//initial first name last name rules with first name character max length set in DB as 20, and middle initial max as 1
|
||||
|
||||
_middleInitial = String.Empty;
|
||||
_firstName = name;
|
||||
|
||||
string[] names = name.Trim().Split(" ");
|
||||
if (names != null && names.Length > 1) //when true there is a middle initial
|
||||
{
|
||||
_firstName = _firstName.Substring(0, name.Length - names.Last().Length).Trim();
|
||||
_middleInitial = names.Last().Substring(0, 1);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
_firstName = name; //no middle initial
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
using ReserveManagementSystemLib.Models;
|
||||
|
||||
namespace ReserveManagementSystemApi.Helpers
|
||||
{
|
||||
public class LoanCalculationHelper
|
||||
{
|
||||
private decimal _daysInterest;
|
||||
private LoanReserve loanReserve = new LoanReserve();
|
||||
|
||||
public LoanCalculationHelper()
|
||||
{
|
||||
|
||||
}
|
||||
public LoanReserve CalculateBuyDown(CalculationConfig cc)
|
||||
{
|
||||
// Case - Buy down Below Threshold where difference in rates is less than or equal to 0.25 percent
|
||||
if (cc.BuydownRateDifference() <= cc.NoReserveThreshold()) //we already know it's positive
|
||||
{
|
||||
loanReserve.ReserveAmount = 0;
|
||||
loanReserve.ReserveTypeMessage = "No Reserve";
|
||||
loanReserve.FlatFeePaid = false;
|
||||
return loanReserve;
|
||||
}
|
||||
|
||||
// Case - This is a regular Buy Down
|
||||
_daysInterest = cc.BuydownRateDifference() / 100 * cc.DaysToFirstPmt() * cc.LoanData().LoanAmount /
|
||||
365 * (decimal)Math.Pow(1 + (double)cc.LoanData().ContractRate / 100, (double)cc.LoanData().Term / 12);
|
||||
|
||||
loanReserve.ReserveAmount = -(cc.BuyFinanceCharge - cc.ContractFinanceCharge + _daysInterest);
|
||||
loanReserve.ReserveTypeMessage = "Buy Down Reserve";
|
||||
loanReserve.FlatFeePaid = false;
|
||||
return loanReserve;
|
||||
}
|
||||
|
||||
public LoanReserve CalculateNoMarkup(CalculationConfig cc)
|
||||
{
|
||||
// Case - Contract rate is equal to the Buy rate
|
||||
_daysInterest = cc.StdRate() * cc.DaysToFirstPmt() * cc.LoanData().LoanAmount /
|
||||
365 * (decimal)Math.Pow(1 + (double)cc.LoanData().ContractRate / 100, (double)cc.LoanData().Term / 12);
|
||||
|
||||
loanReserve.ReserveAmount = (cc.ContractFinanceCharge - cc.BuyFinanceCharge + _daysInterest);
|
||||
loanReserve.ReserveTypeMessage = "No Reserve";
|
||||
loanReserve.FlatFeePaid = false;
|
||||
return loanReserve;
|
||||
}
|
||||
|
||||
public LoanReserve CalculateStandardMarkup(CalculationConfig cc)
|
||||
{
|
||||
// Case - Contract rate is greater than Buy rate
|
||||
_daysInterest = (1 - cc.ReserveDiscount()/100) * cc.StdRate() / 100 * cc.DaysToFirstPmt() * cc.LoanData().LoanAmount /
|
||||
365 * (decimal)Math.Pow(1 + (double)cc.LoanData().ContractRate / 100, (double)cc.LoanData().Term / 12);
|
||||
|
||||
loanReserve.ReserveAmount = ((1 - cc.ReserveDiscount() / 100) * (cc.ContractFinanceCharge - cc.BuyFinanceCharge) + _daysInterest);
|
||||
|
||||
if ((double)cc.StdRate() <= 2.5)
|
||||
{
|
||||
loanReserve.ReserveTypeMessage = "Standard Reserve";
|
||||
}
|
||||
else
|
||||
{
|
||||
loanReserve.ReserveTypeMessage = "Standard Reserve - Exceeds Max Markup";
|
||||
}
|
||||
return loanReserve;
|
||||
}
|
||||
|
||||
public LoanReserve AssignFlatFee(LoanReserve loanReserve, CalculationConfig cc)
|
||||
{
|
||||
loanReserve.FlatFeePaid = false;
|
||||
if (loanReserve.ReserveAmount < (cc.FlatFeePercent() / 100 * cc._loanData.LoanAmount * (1 - cc.ReserveDiscount() / 100)))
|
||||
{
|
||||
loanReserve.ReserveAmount = (cc.FlatFeePercent() / 100 * cc._loanData.LoanAmount * (1 - cc.ReserveDiscount() / 100));
|
||||
loanReserve.ReserveTypeMessage = "Flat Fee Reserve";
|
||||
loanReserve.FlatFeePaid = true;
|
||||
}
|
||||
|
||||
return loanReserve;
|
||||
}
|
||||
|
||||
public decimal FinanceCharge(int term, decimal loanAmount, decimal rate)
|
||||
{
|
||||
if (rate == 0) { return 0; }
|
||||
double paymentRate = (double)rate / (double)1200;
|
||||
decimal rateFactor = (decimal)Math.Pow((1 + paymentRate), (double)term);
|
||||
return (loanAmount * term * rate / 1200 * rateFactor) / (rateFactor - 1) - loanAmount;
|
||||
}
|
||||
|
||||
internal DateTime GetLastChargebackDate(DateTime loanDate, DateTime firstPmtDate, int numOfChargebackPayments, Int16? numOfChargebackDays)
|
||||
{
|
||||
// in rare cases that date from loan date is most recent, check table values and compare between loan date vs. first payment date
|
||||
int result = DateTime.Compare(loanDate.AddDays((Double)numOfChargebackDays), firstPmtDate.AddMonths(numOfChargebackPayments - 1));
|
||||
if (result > 0)
|
||||
{
|
||||
return loanDate.AddDays((Double)numOfChargebackDays); //Loan Date is most recent
|
||||
}
|
||||
return firstPmtDate.AddMonths(numOfChargebackPayments - 1);
|
||||
}
|
||||
|
||||
internal decimal CalculateChargeback(decimal reserve, int term, int remainingMonths)
|
||||
{
|
||||
return reserve * MonthFactor(remainingMonths) / MonthFactor(term);
|
||||
}
|
||||
|
||||
private decimal MonthFactor(int numberOfMonths)
|
||||
{
|
||||
return numberOfMonths * (numberOfMonths + 1) / 2;
|
||||
}
|
||||
|
||||
public int GetRemainingMonths(Loan loanData)
|
||||
{
|
||||
if (loanData.PaidLoanDate >= loanData.FinalPmtDate)
|
||||
return 0;
|
||||
|
||||
var monthsOccurred = (loanData.PaidLoanDate.Value - loanData.FirstPmtDate).TotalDays / (365) * 12 + 1;
|
||||
|
||||
var remainingMonths = (int)(loanData.Term - Math.Round((decimal)monthsOccurred, 0));
|
||||
return remainingMonths;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
using NCrontab;
|
||||
using ReserveManagementSystemApi.Services;
|
||||
|
||||
namespace ReserveManagementSystemApi.HostedService
|
||||
{
|
||||
public class HostedImportExportActive : BackgroundService
|
||||
{
|
||||
private readonly IConfiguration _config;
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly CrontabSchedule _schedule;
|
||||
private DateTime _nextRun;
|
||||
public const string DefaultCronPattern = "0 19 * * 0-6";
|
||||
|
||||
private CancellationToken _cancellationToken;
|
||||
|
||||
public HostedImportExportActive(IConfiguration config, IServiceScopeFactory scopeFactory)
|
||||
{
|
||||
_config = config;
|
||||
_scopeFactory = scopeFactory;
|
||||
try
|
||||
{
|
||||
_schedule = CrontabSchedule.Parse(_config.GetSection("HostedServices").GetSection("ImportExportActiveCronSchedule").Value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure parsing crontab in appsettings.");
|
||||
_schedule = CrontabSchedule.Parse(DefaultCronPattern);
|
||||
}
|
||||
_nextRun = _schedule.GetNextOccurrence(DateTime.Now);
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Log.Information("Hosted ImportExportActive is starting.");
|
||||
Log.Information($"The import export active job will run at {_nextRun:M/d/yyyy HH:mm:ss tt}");
|
||||
|
||||
_cancellationToken = cancellationToken;
|
||||
try
|
||||
{
|
||||
await ImportExportScheduler(cancellationToken);
|
||||
}
|
||||
catch (TaskCanceledException ex) when (_cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
Log.Information(ex, "Hosted ImportExportActive shut down normally.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error trying to startup main thread for Hosted ImportExportActive");
|
||||
}
|
||||
|
||||
Log.Information("Hosted ImportExportActive is stopping.");
|
||||
}
|
||||
|
||||
private async Task ImportExportScheduler(CancellationToken cancellationToken)
|
||||
{
|
||||
do
|
||||
{
|
||||
if (DateTime.Now > _nextRun)
|
||||
{
|
||||
using (var scope = _scopeFactory.CreateScope())
|
||||
{
|
||||
await scope.ServiceProvider.GetRequiredService<IScheduleService>().RunImportExportActive(_cancellationToken);
|
||||
}
|
||||
|
||||
_nextRun = _schedule.GetNextOccurrence(DateTime.Now);
|
||||
}
|
||||
|
||||
await Task.Delay(5000, cancellationToken); //5 seconds delay
|
||||
} while (!cancellationToken.IsCancellationRequested);
|
||||
}
|
||||
}
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
using NCrontab;
|
||||
using ReserveManagementSystemApi.Services;
|
||||
|
||||
namespace ReserveManagementSystemApi.HostedService
|
||||
{
|
||||
public class HostedImportExportClosed : BackgroundService
|
||||
{
|
||||
private readonly IConfiguration _config;
|
||||
private readonly IServiceScopeFactory _scopeFactory;
|
||||
private readonly CrontabSchedule _schedule;
|
||||
private DateTime _nextRun;
|
||||
public const string DefaultCronPattern = "15 19 * * 0-6";
|
||||
|
||||
private CancellationToken _cancellationToken;
|
||||
|
||||
public HostedImportExportClosed(IConfiguration config, IServiceScopeFactory scopeFactory)
|
||||
{
|
||||
_config = config;
|
||||
_scopeFactory = scopeFactory;
|
||||
try
|
||||
{
|
||||
_schedule = CrontabSchedule.Parse(_config.GetSection("HostedServices").GetSection("ImportExportClosedCronSchedule").Value);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure parsing crontab in appsettings.");
|
||||
_schedule = CrontabSchedule.Parse(DefaultCronPattern);
|
||||
}
|
||||
_nextRun = _schedule.GetNextOccurrence(DateTime.Now);
|
||||
}
|
||||
|
||||
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
Log.Information("Hosted ImportExportClosed is starting.");
|
||||
Log.Information($"The import export closed job will run at {_nextRun:M/d/yyyy HH:mm:ss tt}");
|
||||
|
||||
_cancellationToken = cancellationToken;
|
||||
try
|
||||
{
|
||||
await ImportExportScheduler(cancellationToken);
|
||||
}
|
||||
catch (TaskCanceledException ex) when (_cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
Log.Information(ex, "Hosted ImportExportClosed shut down normally.");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error trying to startup main thread for Hosted ImportExportClosed");
|
||||
}
|
||||
|
||||
Log.Information("Hosted ImportExportClosed is stopping.");
|
||||
}
|
||||
|
||||
private async Task ImportExportScheduler(CancellationToken cancellationToken)
|
||||
{
|
||||
do
|
||||
{
|
||||
if (DateTime.Now > _nextRun)
|
||||
{
|
||||
using (var scope = _scopeFactory.CreateScope())
|
||||
{
|
||||
await scope.ServiceProvider.GetRequiredService<IScheduleService>().RunImportExportClosed(_cancellationToken);
|
||||
}
|
||||
|
||||
_nextRun = _schedule.GetNextOccurrence(DateTime.Now);
|
||||
}
|
||||
|
||||
await Task.Delay(5000, cancellationToken); //5 seconds delay
|
||||
} while (!cancellationToken.IsCancellationRequested);
|
||||
}
|
||||
}
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Transaction;
|
||||
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.CoreRequests;
|
||||
using ReserveManagementSystemLib.Models;
|
||||
|
||||
namespace ReserveManagementSystemApi.Mappers
|
||||
{
|
||||
public static class ExportMapper
|
||||
{
|
||||
//used for Chargeback transactions
|
||||
private const string GeneralLedgerReceipt = "GLR";
|
||||
private const string DealerInterestRebate = "DRIR";
|
||||
private const string ChargeBkDesc = "ChrgBk";
|
||||
private const string ChargeBkAdjDesc = "ChrgBk Adj";
|
||||
private const string GeneralLedgerCategory = "NOTE";
|
||||
private const string GeneralLedgerType = "BAL";
|
||||
|
||||
//used for Reserve transactions
|
||||
private const string DealerExpense = "DLRE";
|
||||
private const string DealerIncome = "DLRI";
|
||||
private const string ReserveAdjDesc = "Adjustment";
|
||||
|
||||
//used for all transaction types
|
||||
private const string UpfrontCategory = "UPFR";
|
||||
private const string DealerType = "DLR";
|
||||
private const string ElectronicFundType = "EL";
|
||||
private const string NoHoldClearCategory = "IMED";
|
||||
private const string FundTypeDetail = "INTR";
|
||||
|
||||
public static GroupedMonetaryTransactionRequest MapLoanToIndirectLoanForReserve(Loan loan)
|
||||
{
|
||||
return new GroupedMonetaryTransactionRequest
|
||||
{
|
||||
MonetaryTransactionRequests = BuildReserveTransactions(loan),
|
||||
ParentRequestNumber = loan.LoanNumber
|
||||
};
|
||||
}
|
||||
|
||||
private static List<MonetaryTransactionRequest> BuildReserveTransactions(Loan loan)
|
||||
{
|
||||
var listTransaction = new List<MonetaryTransactionRequest>();
|
||||
var isAdjustment = loan.Adjustment != null && loan.Adjustment != 0;
|
||||
|
||||
var reserveRounded = Math.Round(loan.ReserveAmount, 4, MidpointRounding.AwayFromZero); //this represents the amount saved to DB
|
||||
var amount = isAdjustment ? Math.Round((decimal)loan.Adjustment, 2, MidpointRounding.AwayFromZero) : Math.Round(reserveRounded, 2, MidpointRounding.AwayFromZero);
|
||||
var transactionDescription = isAdjustment ? ReserveAdjDesc : null;
|
||||
|
||||
var loanAmount = (double)amount * -1;
|
||||
listTransaction.Add(BuildParentTransaction(loanAmount, loan.LoanNumber, DealerExpense, transactionDescription));
|
||||
|
||||
var dealerAmount = (double)amount;
|
||||
listTransaction.Add(BuildDealerTransaction(dealerAmount, loan.Dealer.DealerAccountNumber, DealerIncome, transactionDescription));
|
||||
|
||||
return listTransaction;
|
||||
}
|
||||
|
||||
public static GroupedMonetaryTransactionRequest MapLoanToIndirectLoanForChargeback(Loan loan, MinorAccountType accountType)
|
||||
{
|
||||
var request = new GroupedMonetaryTransactionRequest();
|
||||
if (accountType != null) {
|
||||
request.MonetaryTransactionRequests = BuildChargebackTransactions(loan, accountType.GeneralLedgerAccountNumber);
|
||||
request.ParentRequestNumber = loan.LoanNumber;
|
||||
}
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
private static List<MonetaryTransactionRequest> BuildChargebackTransactions(Loan loan, string accountNumber)
|
||||
{
|
||||
var listTransaction = new List<MonetaryTransactionRequest>();
|
||||
_balanceTypeList = new List<BalanceType1>();
|
||||
_fundList = new List<Fund>();
|
||||
_fundBalanceTypeList = new List<FundBalanceType>();
|
||||
|
||||
var isAdjustment = loan.Adjustment != null && loan.Adjustment != 0;
|
||||
|
||||
var chargeBackRounded = Math.Round((decimal)loan.ChargeBack, 4, MidpointRounding.AwayFromZero); //this represents the amount saved to DB
|
||||
var amount = isAdjustment ? Math.Round((decimal)loan.Adjustment, 2, MidpointRounding.AwayFromZero) : Math.Round(chargeBackRounded, 2, MidpointRounding.AwayFromZero);
|
||||
var transactionDescription = isAdjustment ? $"{ChargeBkAdjDesc}-{loan.LoanNumber}" : $"{ChargeBkDesc}-{loan.LoanNumber}";
|
||||
|
||||
var loanAmount = (double)amount;
|
||||
listTransaction.Add(BuildParentTransaction(loanAmount, accountNumber, GeneralLedgerReceipt, transactionDescription));
|
||||
|
||||
var dealerAmount = (double)amount * -1;
|
||||
listTransaction.Add(BuildDealerTransaction(dealerAmount, loan.Dealer.DealerAccountNumber, DealerInterestRebate, transactionDescription));
|
||||
|
||||
return listTransaction;
|
||||
}
|
||||
|
||||
private static MonetaryTransactionRequest BuildParentTransaction(double amount, string accountNumber, string transactionType, string transactionDescription)
|
||||
{
|
||||
//if a reserve the parent transaction contains the loan number, if chargeback the account is the ledger account for minor account type
|
||||
_balanceTypeList = new List<BalanceType1>();
|
||||
_fundList = new List<Fund>();
|
||||
_fundBalanceTypeList = new List<FundBalanceType>();
|
||||
|
||||
_balanceTypeList.Add(BuildBalanceType(amount, transactionType));
|
||||
_fundList.Add(BuildFund(amount));
|
||||
_fundBalanceTypeList.Add(BuildFundBalanceType(amount));
|
||||
|
||||
var transaction = new MonetaryTransactionRequest()
|
||||
{
|
||||
AccountNumber = (long)System.Convert.ToUInt64(accountNumber),
|
||||
TransactionAmount = (transactionType != GeneralLedgerReceipt) ? amount : (double?)null,
|
||||
RequestNumber = "1",
|
||||
ParentRequestNumber = null,
|
||||
TransactionTypeCode = transactionType,
|
||||
Description = transactionDescription,
|
||||
BalanceTypes = _balanceTypeList,
|
||||
Funds = _fundList,
|
||||
FundBalanceTypes = (transactionType == GeneralLedgerReceipt) ? _fundBalanceTypeList : null,
|
||||
};
|
||||
|
||||
return transaction;
|
||||
}
|
||||
|
||||
private static MonetaryTransactionRequest BuildDealerTransaction(double amount, string dealerAccount, string transactionType, string transactionDescription)
|
||||
{
|
||||
//for second request take the offset amount value of parent request amount
|
||||
_balanceTypeList = new List<BalanceType1>();
|
||||
_fundList = new List<Fund>();
|
||||
_fundBalanceTypeList = new List<FundBalanceType>();
|
||||
|
||||
_balanceTypeList.Add(BuildBalanceType(amount, transactionType));
|
||||
_fundList.Add(BuildFund(amount));
|
||||
_fundBalanceTypeList.Add(BuildFundBalanceType(amount));
|
||||
|
||||
var transaction = new MonetaryTransactionRequest()
|
||||
{
|
||||
AccountNumber = (string.IsNullOrEmpty(dealerAccount) == true) ? 0 : (long)System.Convert.ToUInt64(dealerAccount),
|
||||
TransactionAmount = amount,
|
||||
RequestNumber = "2",
|
||||
ParentRequestNumber = "1",
|
||||
TransactionTypeCode = transactionType,
|
||||
Description = transactionDescription,
|
||||
BalanceTypes = _balanceTypeList,
|
||||
Funds = _fundList,
|
||||
};
|
||||
|
||||
return transaction;
|
||||
}
|
||||
|
||||
private static List<FundBalanceType> _fundBalanceTypeList;
|
||||
private static FundBalanceType BuildFundBalanceType(double amount)
|
||||
{
|
||||
return new FundBalanceType
|
||||
{
|
||||
FundBalanceAmount = amount,
|
||||
FundTypeCode = ElectronicFundType,
|
||||
FundTypeDetailCode = NoHoldClearCategory
|
||||
};
|
||||
}
|
||||
|
||||
private static List<BalanceType1> _balanceTypeList;
|
||||
private static BalanceType1 BuildBalanceType(double amount, string transactionType)
|
||||
{
|
||||
return new BalanceType1
|
||||
{
|
||||
BalanceTypeAmount = amount,
|
||||
BalanceCategoryCode = (transactionType == GeneralLedgerReceipt) ? GeneralLedgerCategory : UpfrontCategory,
|
||||
BalanceTypeCode = (transactionType == GeneralLedgerReceipt) ? GeneralLedgerType : DealerType,
|
||||
};
|
||||
}
|
||||
|
||||
private static List<Fund> _fundList;
|
||||
private static Fund BuildFund(double amount)
|
||||
{
|
||||
return new Fund
|
||||
{
|
||||
ClearingCategoryCode = NoHoldClearCategory,
|
||||
FundAmount = amount,
|
||||
FundTypeCode = ElectronicFundType,
|
||||
FundTypeDetailCode = FundTypeDetail,
|
||||
NumberOfItems = 1,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
using CsvHelper.Configuration.Attributes;
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace ReserveManagementSystemApi.Models
|
||||
{
|
||||
public class ImportBoatLoan
|
||||
{
|
||||
|
||||
[Index(0)]
|
||||
public string LoanNumber { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
[Index(1)]
|
||||
public string DealerAccountNumber { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
[Index(2)]
|
||||
public decimal Amount { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
[Index(3)]
|
||||
public string IsChargeback { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
[Index(4)]
|
||||
public string IsAdjustment { get; set; }
|
||||
|
||||
[JsonIgnore]
|
||||
[Index(5)]
|
||||
[Optional]
|
||||
public string AccountType { get; set; }
|
||||
|
||||
[Index(6)]
|
||||
[Optional]
|
||||
public string ErrorMessage { get; set; }
|
||||
|
||||
[Index(7)]
|
||||
[Optional]
|
||||
public bool? WasSuccessful { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
+113
@@ -0,0 +1,113 @@
|
||||
using CsvHelper.Configuration.Attributes;
|
||||
using System;
|
||||
|
||||
namespace ReserveManagementSystemApi.Models
|
||||
{
|
||||
public class ImportNewLoan
|
||||
{
|
||||
[Index(0)]
|
||||
public string DealerOrganizationNumber { get; set; }
|
||||
|
||||
[Index(1)]
|
||||
public string MemberNumber { get; set; }
|
||||
|
||||
[Index(2)]
|
||||
public string LoanNumber { get; set; }
|
||||
|
||||
[Index(3)]
|
||||
public string LastName { get; set; }
|
||||
|
||||
[Index(4)]
|
||||
public string FirstAndMiddleName { get; set; }
|
||||
|
||||
[Index(5)]
|
||||
public DateTime LoanDate { get; set; }
|
||||
|
||||
[Index(6)]
|
||||
public DateTime PostedDate { get; set; }
|
||||
|
||||
[Index(7)]
|
||||
public DateTime FirstPmtDate { get; set; }
|
||||
|
||||
[Index(8)]
|
||||
public DateTime FinalPmtDate { get; set; }
|
||||
|
||||
[Index(9)]
|
||||
public decimal LoanAmount { get; set; }
|
||||
|
||||
[Index(10)]
|
||||
public decimal BuyRate { get; set; }
|
||||
|
||||
[Index(11)]
|
||||
public decimal ContractRate { get; set; }
|
||||
|
||||
[Index(12)]
|
||||
public decimal MonthlyPmt { get; set; }
|
||||
|
||||
[Index(13)]
|
||||
public string LoanType { get; set; }
|
||||
|
||||
[Index(14)]
|
||||
public string InsuranceIndicator { get; set; }
|
||||
|
||||
[Index(15)]
|
||||
public string NewMember { get; set; }
|
||||
|
||||
[Index(16)]
|
||||
[Optional]
|
||||
public string OriginalTermInDays { get; set; }
|
||||
|
||||
[Index(17)]
|
||||
[Optional]
|
||||
public string VINNumber { get; set; }
|
||||
|
||||
[Index(18)]
|
||||
[Optional]
|
||||
public string Make { get; set; }
|
||||
|
||||
[Index(19)]
|
||||
[Optional]
|
||||
public string Model { get; set; }
|
||||
|
||||
[Index(20)]
|
||||
[Optional]
|
||||
public string Year { get; set; }
|
||||
|
||||
[Index(21)]
|
||||
[Optional]
|
||||
public string Mileage { get; set; }
|
||||
|
||||
[Index(22)]
|
||||
[Optional]
|
||||
public string Color { get; set; }
|
||||
|
||||
[Index(23)]
|
||||
[Optional]
|
||||
public string ApplicationId { get; set; }
|
||||
|
||||
[Index(24)]
|
||||
[Optional]
|
||||
public string Warranty { get; set; }
|
||||
|
||||
[Index(25)]
|
||||
[Optional]
|
||||
public string Gap { get; set; }
|
||||
|
||||
[Index(26)]
|
||||
[Optional]
|
||||
public string CreditLife { get; set; }
|
||||
|
||||
[Index(27)]
|
||||
[Optional]
|
||||
public string CreditDisability { get; set; }
|
||||
|
||||
[Index(28)]
|
||||
[Optional]
|
||||
public string Mbp { get; set; }
|
||||
|
||||
[Index(29)]
|
||||
[Optional]
|
||||
public string AccountType { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
+249
@@ -0,0 +1,249 @@
|
||||
using ReserveManagementSystemApi.Services;
|
||||
using ReserveManagementSystemApi.HostedService;
|
||||
using ReserveManagementSystemApi.ServiceClients;
|
||||
using ReserveManagementSystemApi.Configurations;
|
||||
using ReserveManagementSystemLib.Configuration;
|
||||
using ReserveManagementSystemLib.Persistence;
|
||||
using ReserveManagementSystemLib.Repositories;
|
||||
using MassTransit;
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Microsoft.Extensions.FileProviders;
|
||||
using Newtonsoft.Json;
|
||||
using LCU.Library.Internal.LcuHttpClient;
|
||||
using LCU.Library.Internal.ApiSecurity;
|
||||
using LCU.Library.Internal.Logging.Models;
|
||||
using LCU.Library.Internal.Logging;
|
||||
using LCU.Library.Internal.Logging.Middleware;
|
||||
using LCU.Library.Internal.Configuration;
|
||||
using LCU.Library.Internal.LcuMassTransit;
|
||||
using LCU.Library.Internal.LcuMassTransitModels;
|
||||
using ReserveManagementSystemApi.Consumers;
|
||||
using ReserveManagementSystemApi.Services.QueueServices;
|
||||
using LCU.Library.Internal.Status;
|
||||
|
||||
namespace ReserveManagementSystemApi
|
||||
{
|
||||
public class Program
|
||||
{
|
||||
public static string MyAllowSpecificOrigins { get; private set; }
|
||||
|
||||
static void Main(string[] args)
|
||||
{
|
||||
MyAllowSpecificOrigins = "_myAllowSpecificOrigins";
|
||||
WebApplicationBuilder builder = BuildBuilder(args);
|
||||
BuildServicesAndConfigurations(builder);
|
||||
BuildAppAndRun(builder);
|
||||
}
|
||||
|
||||
public static WebApplicationBuilder BuildBuilder(string[] args)
|
||||
{
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Configuration.AddJsonFile("/app/config/environment-appsettings/appsettings.json", true, true);
|
||||
builder.Configuration.AddJsonFile("/app/config/workload-appsettings/appsettings.json", true, true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/reserve-management-system-seq-api-key.json", true, true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/reserve-management-system-rms-database-connection.json", true, true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/reserve-management-system-connection-configuration-queue-username.json", true, true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/reserve-management-system-connection-configuration-queue-password.json", true, true);
|
||||
builder.Configuration.AddJsonFile("/vault/secrets/reserve-management-system-api-security-configuration-internal-api-key-set.json", true, true);
|
||||
builder.Host.UseSerilog();
|
||||
return builder;
|
||||
}
|
||||
|
||||
public static void BuildServicesAndConfigurations(WebApplicationBuilder builder)
|
||||
{
|
||||
var services = builder.Services;
|
||||
var configuration = builder.Configuration;
|
||||
|
||||
services.AddControllers();
|
||||
services.AddEndpointsApiExplorer();
|
||||
services.AddLCUHttpServices();
|
||||
|
||||
var apiConfiguration = configuration.Bind<ApiConfiguration>(services, "Primary");
|
||||
|
||||
services.AddCors(options =>
|
||||
{
|
||||
options.AddPolicy(MyAllowSpecificOrigins,
|
||||
builder =>
|
||||
{
|
||||
builder.WithOrigins(apiConfiguration.CorsAllowedOrigins).AllowAnyHeader()
|
||||
.AllowAnyMethod();
|
||||
});
|
||||
});
|
||||
|
||||
services.AddDbContext<AppDbContext>(options =>
|
||||
{
|
||||
options.UseSqlServer(configuration.GetSection("SqlConnectionConfiguration").GetSection("ConnectionString").Value);
|
||||
});
|
||||
|
||||
var connectionConfiguration = MassTransitConfigurationSetUp.AddConnectionConfiguration(ref services, configuration);
|
||||
Log.Debug(JsonConvert.SerializeObject(connectionConfiguration));
|
||||
|
||||
var receiveEndpointConfigurations = new ReceiveEndpointConfigurations()
|
||||
{
|
||||
RmsActiveLoanReceiveEndpoint = MassTransitConfigurationSetUp.GetReceiveEndpointConfiguration(configuration, "RmsActiveLoanReceiveEndpoint")
|
||||
};
|
||||
|
||||
services.AddSingleton(receiveEndpointConfigurations);
|
||||
Log.Debug(JsonConvert.SerializeObject(receiveEndpointConfigurations));
|
||||
|
||||
AddMassTransitServices(ref services, connectionConfiguration, receiveEndpointConfigurations, apiConfiguration);
|
||||
AddQueueServices(services);
|
||||
|
||||
var loggerConfiguration = new LoggingConfiguration(apiConfiguration.AppName,
|
||||
apiConfiguration.SeqApiKey,
|
||||
apiConfiguration.SeqUrl);
|
||||
var lcuLogging = new LcuLogging(loggerConfiguration);
|
||||
lcuLogging.UseLcuLogging();
|
||||
|
||||
var rmsApiConfiguration = configuration.Bind<RmsApiConfiguration>(services, "RmsApiConfiguration");
|
||||
var coreApiConfiguration = configuration.Bind<CoreApiConfiguration>(services, "CoreApiConfiguration");
|
||||
|
||||
services.AddApiKeySecurity(configuration);
|
||||
services.AddLCUStatusServices();
|
||||
|
||||
//Services
|
||||
services.AddScoped<IStatusService, StatusService>();
|
||||
services.AddScoped<ILoanCalculationService, LoanCalculationService>();
|
||||
services.AddScoped<ILoanService, LoanService>();
|
||||
services.AddScoped<IImportService, ImportService>();
|
||||
services.AddScoped<IImportReadService, ImportReadService>();
|
||||
services.AddScoped<IAuditHistoryService, AuditHistoryService>();
|
||||
services.AddScoped<IDealerService, DealerService>();
|
||||
services.AddScoped<IExportService, ExportService>();
|
||||
services.AddScoped<IVehicleService, VehicleService>();
|
||||
services.AddScoped<IScheduleService, ScheduleService>();
|
||||
services.AddScoped<IRmsActiveLoanService, RmsActiveLoanService>();
|
||||
|
||||
//Service Clients
|
||||
services.AddScoped<ICoreApiServiceClient, CoreApiServiceClient>();
|
||||
|
||||
//Repositories
|
||||
services.AddScoped<ILoanRepository, LoanRepository>();
|
||||
services.AddScoped<ILoanTypeRepository, LoanTypeRepository>();
|
||||
services.AddScoped<IImportRepository, ImportRepository>();
|
||||
services.AddScoped<IAuditHistoryRepository, AuditHistoryRepository>();
|
||||
services.AddScoped<IDealerRepository, DealerRepository>();
|
||||
services.AddScoped<IVehicleRepository, VehicleRepository>();
|
||||
services.AddScoped<IMinorAccountTypeRepository, MinorAccountTypeRepository>();
|
||||
|
||||
//HostedServices
|
||||
if (apiConfiguration.FeatureFlags.EnableImportExportActive)
|
||||
{
|
||||
services.AddHostedService<HostedImportExportActive>();
|
||||
}
|
||||
|
||||
if (apiConfiguration.FeatureFlags.EnableImportExportClosed)
|
||||
{
|
||||
services.AddHostedService<HostedImportExportClosed>();
|
||||
}
|
||||
|
||||
services.AddSwaggerGen(c =>
|
||||
{
|
||||
c.SwaggerDoc("v1", new OpenApiInfo
|
||||
{
|
||||
Title = $"{apiConfiguration.AppName}",
|
||||
Version = "v1",
|
||||
Contact = new OpenApiContact { Name = "SpecFlow Tests", Url = new Uri("/Specs/index.html", UriKind.Relative) }
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
public static void BuildAppAndRun(WebApplicationBuilder builder)
|
||||
{
|
||||
var app = builder.Build();
|
||||
|
||||
app.UseSwagger(c =>
|
||||
{
|
||||
c.PreSerializeFilters.Add((swaggerDoc, request) =>
|
||||
{
|
||||
const string prefixHeader = "X-Forwarded-Prefix";
|
||||
if (!request.Headers.ContainsKey(prefixHeader)) return;
|
||||
|
||||
var serverUrl = request.Headers[prefixHeader];
|
||||
swaggerDoc.Servers = new List<OpenApiServer>()
|
||||
{
|
||||
new() { Description = serverUrl, Url = serverUrl }
|
||||
};
|
||||
});
|
||||
});
|
||||
app.UseSwaggerUI(c =>
|
||||
{
|
||||
c.RoutePrefix = "swagger";
|
||||
c.SwaggerEndpoint("v1/swagger.json", "ReserveManagementSystemApi v1");
|
||||
});
|
||||
|
||||
if (app.Environment.IsDevelopment())
|
||||
{
|
||||
app.UseDeveloperExceptionPage();
|
||||
}
|
||||
|
||||
var specsFolder = Path.Combine(Directory.GetCurrentDirectory(), "Specs");
|
||||
|
||||
if (Directory.Exists(specsFolder))
|
||||
{
|
||||
app.UseFileServer(new FileServerOptions()
|
||||
{
|
||||
FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "Specs")),
|
||||
RequestPath = new PathString("/Specs"),
|
||||
EnableDefaultFiles = true
|
||||
});
|
||||
}
|
||||
|
||||
app.UseMiddleware<RequestResponseLoggingMiddleware>();
|
||||
app.UseSerilogRequestLogging(options =>
|
||||
{
|
||||
options.EnrichDiagnosticContext = RequestResponseLoggingMiddleware.RequestDataParser.EnrichFromRequest;
|
||||
});
|
||||
|
||||
app.UseAuthentication();
|
||||
app.UseHttpsRedirection();
|
||||
app.UseRouting();
|
||||
app.UseAuthorization();
|
||||
|
||||
app.UseEndpoints(endpoints =>
|
||||
{
|
||||
endpoints.MapControllers();
|
||||
});
|
||||
|
||||
app.Run();
|
||||
}
|
||||
|
||||
public static void AddMassTransitServices(ref IServiceCollection services,
|
||||
ConnectionConfiguration connectionConfiguration,
|
||||
ReceiveEndpointConfigurations receiveEndpointConfigurations,
|
||||
ApiConfiguration apiConfiguration)
|
||||
{
|
||||
services.AddMassTransit(x =>
|
||||
{
|
||||
x.AddConsumer<RmsActiveImportConsumer>();
|
||||
|
||||
x.UsingRabbitMq((context, cfg) =>
|
||||
{
|
||||
cfg.Host(connectionConfiguration.HostUrl, "/", host =>
|
||||
{
|
||||
host.Username(connectionConfiguration.UserName);
|
||||
host.Password(connectionConfiguration.Password);
|
||||
});
|
||||
cfg.UseTimeout(t => t.Timeout = TimeSpan.FromSeconds(420));
|
||||
|
||||
if (apiConfiguration.FeatureFlags.RunRmsActiveLoanConsumer)
|
||||
{
|
||||
cfg.ReceiveEndpoint(receiveEndpointConfigurations.RmsActiveLoanReceiveEndpoint.ReceiveEndpoint, e =>
|
||||
{
|
||||
e.ConfigureConsumer<RmsActiveImportConsumer>(context);
|
||||
e.SetQuorumQueue();
|
||||
e.PrefetchCount = receiveEndpointConfigurations.RmsActiveLoanReceiveEndpoint.PrefetchLimit;
|
||||
e.UseTimeout(t => t.Timeout = TimeSpan.FromSeconds(420));
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
services.AddMassTransitHostedService();
|
||||
}
|
||||
|
||||
public static void AddQueueServices(IServiceCollection services)
|
||||
{
|
||||
services.AddScoped<IQueueRmsActiveLoanService, QueueRmsActiveLoanService>();
|
||||
}
|
||||
}
|
||||
}
|
||||
+45
@@ -0,0 +1,45 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net6.0</TargetFramework>
|
||||
<Version>1.1.1</Version>
|
||||
<GeneratePackageOnBuild>false</GeneratePackageOnBuild>
|
||||
<Description></Description>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CsvHelper" Version="30.0.1" />
|
||||
<PackageReference Include="LCU.Api.Internal.MaidBotLib" Version="1.0.2" />
|
||||
<PackageReference Include="LCU.Library.Internal.ApiSecurity" Version="1.0.2" />
|
||||
<PackageReference Include="LCU.Library.Internal.Configuration" Version="1.0.0" />
|
||||
<PackageReference Include="LCU.Library.Internal.CoreApiWrapperModel" Version="2.3.0" />
|
||||
<PackageReference Include="LCU.Library.Internal.Exceptions" Version="1.0.0" />
|
||||
<PackageReference Include="LCU.Library.Internal.LcuHttpClient" Version="1.9.0" />
|
||||
<PackageReference Include="LCU.Library.Internal.LcuMassTransit" Version="1.0.0" />
|
||||
<PackageReference Include="LCU.Library.Internal.LcuMassTransitModels" Version="1.1.0" />
|
||||
<PackageReference Include="LCU.Library.Internal.Logging" Version="1.1.0" />
|
||||
<PackageReference Include="LCU.Library.Internal.Status" Version="1.0.1" />
|
||||
<PackageReference Include="LCU.Library.Internal.StatusModels" Version="1.0.0" />
|
||||
<PackageReference Include="LCU.Shared.Common.NotificationApiLib" Version="2.0.0" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Hosting" Version="2.2.7" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.18" />
|
||||
<PackageReference Include="Microsoft.Extensions.Configuration" Version="7.0.0" />
|
||||
<PackageReference Include="Microsoft.NETCore.App" Version="2.2.8" />
|
||||
<PackageReference Include="NCrontab.Signed" Version="3.3.2" />
|
||||
<PackageReference Include="Pickles.CommandLine" Version="2.21.1" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.2.12" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\ReserveManagementSystemLib\ReserveManagementSystemLib.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Specs\**">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio Version 16
|
||||
VisualStudioVersion = 16.0.31025.194
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReserveManagementSystemApi", "ReserveManagementSystemApi.csproj", "{954C8B67-1471-4FB2-81BD-713BEE5E679E}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReserveManagementSystemLib", "..\ReserveManagementSystemLib\ReserveManagementSystemLib.csproj", "{BA77A7A0-7D17-4BD8-80A4-B9FFB3908C82}"
|
||||
EndProject
|
||||
Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "ReserveManagementSystemApi.Sql", "..\ReserveManagementSystemApi.Sql\ReserveManagementSystemApi.Sql.sqlproj", "{5A41C8DC-6F4D-4FD2-B03D-D0B2D8B363DF}"
|
||||
EndProject
|
||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReserveManagementSystemApi.Tests", "..\ReserveManagementSystemApi.Tests\ReserveManagementSystemApi.Tests.csproj", "{CFE504B7-58D7-444B-A4B8-8A219400BA42}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReserveManagementSystemApi.Test", "..\ReserveManagementSystemApi.Test\ReserveManagementSystemApi.Test.csproj", "{5BC5FBBC-2B72-4055-95E0-41192F581784}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{954C8B67-1471-4FB2-81BD-713BEE5E679E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{954C8B67-1471-4FB2-81BD-713BEE5E679E}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{954C8B67-1471-4FB2-81BD-713BEE5E679E}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{954C8B67-1471-4FB2-81BD-713BEE5E679E}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{BA77A7A0-7D17-4BD8-80A4-B9FFB3908C82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{BA77A7A0-7D17-4BD8-80A4-B9FFB3908C82}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{BA77A7A0-7D17-4BD8-80A4-B9FFB3908C82}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{BA77A7A0-7D17-4BD8-80A4-B9FFB3908C82}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5A41C8DC-6F4D-4FD2-B03D-D0B2D8B363DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5A41C8DC-6F4D-4FD2-B03D-D0B2D8B363DF}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5A41C8DC-6F4D-4FD2-B03D-D0B2D8B363DF}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{5A41C8DC-6F4D-4FD2-B03D-D0B2D8B363DF}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5A41C8DC-6F4D-4FD2-B03D-D0B2D8B363DF}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5A41C8DC-6F4D-4FD2-B03D-D0B2D8B363DF}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||
{CFE504B7-58D7-444B-A4B8-8A219400BA42}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{CFE504B7-58D7-444B-A4B8-8A219400BA42}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{CFE504B7-58D7-444B-A4B8-8A219400BA42}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{CFE504B7-58D7-444B-A4B8-8A219400BA42}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{5BC5FBBC-2B72-4055-95E0-41192F581784}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{5BC5FBBC-2B72-4055-95E0-41192F581784}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{5BC5FBBC-2B72-4055-95E0-41192F581784}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{5BC5FBBC-2B72-4055-95E0-41192F581784}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {27D5180A-D1E5-4786-9F4D-99C4652488F4}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
+120
@@ -0,0 +1,120 @@
|
||||
using ReserveManagementSystemLib.Configuration;
|
||||
using LCU.Library.Internal.ApiSecurity.Models;
|
||||
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.IndirectLoan;
|
||||
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.CoreRequests;
|
||||
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Transaction;
|
||||
using LCU.Library.Internal.LcuHttpClient.HttpRequest;
|
||||
using LCU.Library.Internal.ApiSecurity.Enums;
|
||||
using LCU.Library.Internal.LcuHttpClient.Utilities;
|
||||
|
||||
namespace ReserveManagementSystemApi.ServiceClients
|
||||
{
|
||||
public class CoreApiServiceClient : ICoreApiServiceClient
|
||||
{
|
||||
private readonly ApiSecurityConfiguration _apiSecurityConfiguration;
|
||||
private readonly CoreApiConfiguration _coreApiWrapperConfiguration;
|
||||
private readonly IPostRequest _postRequest;
|
||||
private readonly IGetRequest _getRequest;
|
||||
private readonly IPutRequest _putRequest;
|
||||
|
||||
private const string ClientKeyValue = "ReserveManagementSystem";
|
||||
|
||||
public CoreApiServiceClient(
|
||||
ApiSecurityConfiguration apiSecurityConfiguration,
|
||||
CoreApiConfiguration coreApiWrapperConfiguration,
|
||||
IGetRequest getRequest,
|
||||
IPostRequest postRequest,
|
||||
IPutRequest putRequest
|
||||
)
|
||||
{
|
||||
_apiSecurityConfiguration = apiSecurityConfiguration;
|
||||
_coreApiWrapperConfiguration = coreApiWrapperConfiguration;
|
||||
_getRequest = getRequest;
|
||||
_postRequest = postRequest;
|
||||
_putRequest = putRequest;
|
||||
}
|
||||
|
||||
public async Task<List<MonetaryTransactionResponse>> PostMonetaryTransactions(List<GroupedMonetaryTransactionRequest> loanReserves)
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = $"{_coreApiWrapperConfiguration.BaseUrl}{_coreApiWrapperConfiguration.PostMonetaryTransactions}";
|
||||
var uri = new Uri(path);
|
||||
var response = await _postRequest.Async<List<GroupedMonetaryTransactionRequest>, List<MonetaryTransactionResponse>>(uri, loanReserves,
|
||||
HttpUtilities.GetApiKeyHeaders(ClientKeyValue, _apiSecurityConfiguration.ExtractApiKey(ClientKeyValue, ApiKeySetName.InternalKeySet)),
|
||||
0, Convert.ToInt16(_coreApiWrapperConfiguration.ApiTimeOutInSeconds));
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error in posting MonetaryTransactions.");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<UpdateDealerOriginalBalanceResponse>> PutDealerOriginalBalance(string accountNumber)
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = $"{_coreApiWrapperConfiguration.BaseUrl}{_coreApiWrapperConfiguration.PutDealerOriginalBalance}";
|
||||
var uri = new Uri(path);
|
||||
var response = await _putRequest.Async<string, List<UpdateDealerOriginalBalanceResponse>>(uri, accountNumber,
|
||||
HttpUtilities.GetApiKeyHeaders(ClientKeyValue, _apiSecurityConfiguration.ExtractApiKey(ClientKeyValue, ApiKeySetName.InternalKeySet)),
|
||||
0, Convert.ToUInt16(_coreApiWrapperConfiguration.ApiTimeOutInSeconds));
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error updating Dealer Original Balance");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<IndirectLoanDetail>> GetIndirectLoansByPostedDate(string afterPostDate)
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = $"{_coreApiWrapperConfiguration.BaseUrl}{_coreApiWrapperConfiguration.GetIndirectLoansByPostedDate}{afterPostDate}";
|
||||
var response = await _getRequest.AsyncWithNewton<List<IndirectLoanDetail>>(path, null, 0, Convert.ToUInt16(_coreApiWrapperConfiguration.ApiTimeOutInSeconds));
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error getting Indirect Loans By Posted Date");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<ClosedIndirectLoan>> GetClosedLoansByPostedDate(string afterPostDate)
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = $"{_coreApiWrapperConfiguration.BaseUrl}{_coreApiWrapperConfiguration.GetClosedLoansByPostedDate}{afterPostDate}";
|
||||
var response = await _getRequest.AsyncWithNewton<List<ClosedIndirectLoan>>(path);
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error getting Closed Loans By Posted Date");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public async Task<List<IndirectLoanDetail>> GetClosedLoansWithDetailByClosedDate(string afterClosedDate)
|
||||
{
|
||||
try
|
||||
{
|
||||
var path = $"{_coreApiWrapperConfiguration.BaseUrl}{_coreApiWrapperConfiguration.GetClosedIndirectLoansWithDetailByPostedDate}{afterClosedDate}";
|
||||
var response = await _getRequest.AsyncWithNewton<List<IndirectLoanDetail>>(path);
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error getting Closed Loans With Detail");
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.IndirectLoan;
|
||||
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Transaction;
|
||||
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.CoreRequests;
|
||||
|
||||
namespace ReserveManagementSystemApi.ServiceClients
|
||||
{
|
||||
public interface ICoreApiServiceClient
|
||||
{
|
||||
Task<List<MonetaryTransactionResponse>> PostMonetaryTransactions(List<GroupedMonetaryTransactionRequest> loans);
|
||||
|
||||
Task<List<IndirectLoanDetail>> GetIndirectLoansByPostedDate(string afterPostDate);
|
||||
|
||||
Task<List<ClosedIndirectLoan>> GetClosedLoansByPostedDate(string afterPostDate);
|
||||
|
||||
Task<List<IndirectLoanDetail>> GetClosedLoansWithDetailByClosedDate(string afterClosedDate);
|
||||
|
||||
Task<List<UpdateDealerOriginalBalanceResponse>> PutDealerOriginalBalance(string accountNumber);
|
||||
}
|
||||
}
|
||||
+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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
global using Microsoft.AspNetCore.Mvc;
|
||||
global using Microsoft.EntityFrameworkCore;
|
||||
global using Serilog;
|
||||
global using System;
|
||||
global using System.Linq;
|
||||
global using System.Threading.Tasks;
|
||||
global using LCU.Library.Internal.Exceptions;
|
||||
|
||||
+59
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"Primary": {
|
||||
"AppName": "Reserve Management System",
|
||||
"SeqUrl": "http://localhost",
|
||||
"SeqApiKey": "VALUE-IN-KEYVAULT",
|
||||
"CorsAllowedOrigins": [
|
||||
"http://localhost:3000",
|
||||
"http://lcu-internal.com",
|
||||
"https://lcu-internal.com",
|
||||
"https://qa-api.lcu-internal.com"
|
||||
],
|
||||
"FeatureFlags": {
|
||||
"RunRmsActiveLoanConsumer": true,
|
||||
"EnableImportExportActive": false,
|
||||
"EnableImportExportClosed": false
|
||||
}
|
||||
},
|
||||
"SqlConnectionConfiguration": {
|
||||
"ConnectionString": "data source=localhost;initial catalog=ReserveManagementSystem;Integrated Security=SSPI;MultipleActiveResultSets=True;App=EntityFramework",
|
||||
"MaxRetryCount": 3,
|
||||
"MaxRetryDelay": "00:00:30"
|
||||
},
|
||||
"ConnectionConfiguration": {
|
||||
"HostUrl": "localhost",
|
||||
"UserName": "guest",
|
||||
"Password": "guest"
|
||||
},
|
||||
"RmsActiveLoanReceiveEndpoint": {
|
||||
"ReceiveEndpoint": "LCU.ReserveManagementSystem.ActiveImport",
|
||||
"ReceiveEndpointType": "exchange",
|
||||
"PrefetchLimit": 1
|
||||
},
|
||||
"ApiSecurityConfiguration": {
|
||||
"ApiKeyAuthenticationFailureBehavior": "Error",
|
||||
"InternalApiKeySet": {
|
||||
"ReserveManagementSystem": "VALUE-IN-KEYVAULT"
|
||||
},
|
||||
"ExternalApiKeySet": {}
|
||||
},
|
||||
"CoreApiConfiguration": {
|
||||
"ApiTimeOutInSeconds": 420,
|
||||
"BaseUrl": "https://localhost:59303",
|
||||
"GetIndirectLoansByPostedDate": "/api/core/IndirectLoan/",
|
||||
"GetClosedLoansByPostedDate": "/api/core/IndirectLoan/Closed/",
|
||||
"GetClosedIndirectLoansWithDetailByPostedDate": "/api/core/IndirectLoan/ClosedWithDetail/",
|
||||
"PostMonetaryTransactions": "/api/core/Transaction/PostMonetaryTransactions",
|
||||
"PutDealerOriginalBalance": "/api/core/IndirectLoan/PutDealerOriginalBalance"
|
||||
},
|
||||
"AllowedHosts": "*",
|
||||
"HostedServices": {
|
||||
"ImportExportActiveCronSchedule": "0 19 * * 0-6",
|
||||
"ImportExportClosedCronSchedule": "15 19 * * 0-6"
|
||||
},
|
||||
"RmsApiConfiguration": {
|
||||
"DefaultImportStartDate": "2021-07-01 00:00:00",
|
||||
"DefaultBatchSize": 100,
|
||||
"DefaultConvertedLoanPostDate": "2021-10-31"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user