Adding
This commit is contained in:
+461
@@ -0,0 +1,461 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Exceptions;
|
||||
using LCU.API.Collections.RepoApi.Models.Filters;
|
||||
using LCU.API.Collections.RepoApi.Models.Requests;
|
||||
using LCU.Shared.Collections.RepoApiLib.Exceptions;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Results;
|
||||
using LCU.Shared.Collections.RepoApiLib.Repositories;
|
||||
using LCU.Shared.Collections.RepoApiLib.WebServices.AutoIMS;
|
||||
using Serilog;
|
||||
using AuctionAssignment = LCU.Shared.Collections.RepoApiLib.Models.AuctionAssignment;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public class AuctionAssignmentService : IAuctionAssignmentService
|
||||
{
|
||||
private readonly IAuctionAssignmentRepository _auctionAssignmentRepository;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly IEmailer _emailer;
|
||||
private readonly IAutoImsWebService _autoImsWebService;
|
||||
private readonly IRepoAccountService _repoAccountService;
|
||||
|
||||
public AuctionAssignmentService(
|
||||
IAutoImsWebService autoImsWebService,
|
||||
IRepoAccountService repoAccountService,
|
||||
IAuctionAssignmentRepository auctionAssignmentRepository,
|
||||
IUnitOfWork unitOfWork,
|
||||
IEmailer emailer)
|
||||
{
|
||||
_autoImsWebService = autoImsWebService;
|
||||
_repoAccountService = repoAccountService;
|
||||
_auctionAssignmentRepository = auctionAssignmentRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
_emailer = emailer;
|
||||
}
|
||||
|
||||
public async Task<PagedResult<AuctionAssignment>> ListByFilterAsync(AuctionAssignmentFilter filter)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (filter.ReadyForFloorPrice)
|
||||
{
|
||||
return await _auctionAssignmentRepository.ListReadyForFloorPriceAsync();
|
||||
}
|
||||
return await _auctionAssignmentRepository.ListAllAsync(filter.Page, filter.Limit);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving AuctionAssignments");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<AuctionAssignment>> GetListByIdsAsync(IReadOnlyCollection<int> idSet)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _auctionAssignmentRepository.FindListByIdsAsync(idSet);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving AuctionAssignments for ids: {@idSet}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AuctionAssignment> GetByIdAsync(int id)
|
||||
{
|
||||
AuctionAssignment auctionAssignment;
|
||||
try
|
||||
{
|
||||
auctionAssignment = await _auctionAssignmentRepository.FindByIdAsync(id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving AuctionAssignment with id: {id}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
|
||||
if (auctionAssignment == null)
|
||||
{
|
||||
throw new NotFoundException($" AuctionAssignment with {id} not found.");
|
||||
}
|
||||
|
||||
return auctionAssignment;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<AuctionAssignment>> GetByRepoIdAsync(int repoId)
|
||||
{
|
||||
// Ensure the repo exists. If it doesn't, a NFE should be thrown here
|
||||
await _repoAccountService.Get(repoId);
|
||||
try
|
||||
{
|
||||
return await _auctionAssignmentRepository.FindByRepoIdAsync(repoId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving AuctionAssignments by RepoId with RepoID: {repoId}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AuctionAssignmentPostRequest> SaveAsync(AuctionAssignmentPostRequest auctionAssignmentPostRequest)
|
||||
{
|
||||
Collateral primaryCollateral = auctionAssignmentPostRequest.Collaterals
|
||||
.First(collateral => collateral.CollateralId == auctionAssignmentPostRequest.PrimaryCollateralId);
|
||||
|
||||
string auctionCustomerNo;
|
||||
try
|
||||
{
|
||||
auctionCustomerNo = await GetAuctionCustomerNoAsync(auctionAssignmentPostRequest.AuctionAssignment.RepoId.Value, primaryCollateral.RefCollateralId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving Auction Customer Number with auctionAssignmentPostRequest {auctionAssignmentPostRequest}", auctionAssignmentPostRequest);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
|
||||
// If active auction already exists, do not create
|
||||
var conflictingAuctionAssignments = await _findActiveAuctionsWithMatchingCollateral(primaryCollateral.RepoId, auctionAssignmentPostRequest.Collaterals);
|
||||
if (conflictingAuctionAssignments!= null && conflictingAuctionAssignments.Any())
|
||||
{
|
||||
throw new DuplicateResourceException("AuctionAssignment");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var auctionAssignmentsToCollaterals = CreateJoiningAuctionAssignmentToCollateralsList(
|
||||
auctionAssignmentPostRequest.AuctionAssignment, auctionAssignmentPostRequest.Collaterals);
|
||||
|
||||
auctionAssignmentPostRequest.AuctionAssignment.AuctionCustomerNo = auctionCustomerNo;
|
||||
auctionAssignmentPostRequest.AuctionAssignment.DateSubmitted = DateTime.Now;
|
||||
auctionAssignmentPostRequest.AuctionAssignment.CollateralVIN = primaryCollateral.Vin;
|
||||
auctionAssignmentPostRequest.AuctionAssignment.AuctionAssignmentToCollaterals = auctionAssignmentsToCollaterals;
|
||||
|
||||
auctionAssignmentPostRequest.AuctionAssignment = await _auctionAssignmentRepository.AddAsync(auctionAssignmentPostRequest.AuctionAssignment);
|
||||
|
||||
await _unitOfWork.CompleteAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure creating AuctionAssignment object: {auctionAssignmentPostRequest.AuctionAssignment}.", auctionAssignmentPostRequest.AuctionAssignment);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await CreateAutoIMSAuction(primaryCollateral, auctionAssignmentPostRequest.VehicleLocation, auctionAssignmentPostRequest.AuctionAssignment);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure creating AutoIMS Auction with Primary Collateral: {primaryCollateral} Removing AuctionAssignment...", primaryCollateral);
|
||||
|
||||
try { _auctionAssignmentRepository.Remove(auctionAssignmentPostRequest.AuctionAssignment); }
|
||||
catch (Exception ex2) { Log.Error(ex2, $"Failure removing AuctionAssignment : {auctionAssignmentPostRequest.AuctionAssignment} after failed AutoIMS creation.", auctionAssignmentPostRequest.AuctionAssignment); }
|
||||
|
||||
throw new WebException("Failed to add auction assignment to AutoIMS.");
|
||||
}
|
||||
|
||||
return auctionAssignmentPostRequest;
|
||||
}
|
||||
|
||||
public async Task<AuctionAssignment> UpdateAsync(AuctionAssignment auctionAssignment)
|
||||
{
|
||||
var existingAuctionAssignment = await _auctionAssignmentRepository.FindByIdAsync(auctionAssignment.ID);
|
||||
|
||||
if (existingAuctionAssignment == null)
|
||||
{
|
||||
throw new NotFoundException($" Auction Assignment with id {auctionAssignment.ID} not found.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (existingAuctionAssignment.FloorPrice != auctionAssignment.FloorPrice)
|
||||
{
|
||||
await UpdateAuctionFloor(auctionAssignment, existingAuctionAssignment);
|
||||
}
|
||||
|
||||
if (existingAuctionAssignment.SentTitleDocsDate != auctionAssignment.SentTitleDocsDate)
|
||||
{
|
||||
await UpdateTitleSentDate(auctionAssignment, existingAuctionAssignment);
|
||||
}
|
||||
|
||||
if (existingAuctionAssignment.RedeemedDate != auctionAssignment.RedeemedDate)
|
||||
{
|
||||
await UpdateRedeemedDate(auctionAssignment, existingAuctionAssignment);
|
||||
}
|
||||
|
||||
if (existingAuctionAssignment.HoldStatus != auctionAssignment.HoldStatus || existingAuctionAssignment.HoldUntil != auctionAssignment.HoldUntil)
|
||||
{
|
||||
await UpdateHoldData(auctionAssignment, existingAuctionAssignment);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failed to fully update auction assignment with id {auctionAssignment.ID} in AutoIMS");
|
||||
throw new WebException("Failed to fully update auction assignment in AutoIMS.");
|
||||
}
|
||||
|
||||
return auctionAssignment;
|
||||
}
|
||||
|
||||
public async Task<string> GetAuctionCustomerNoAsync(int repoId, string refCollateralId)
|
||||
{
|
||||
RepoAccount repoAccount = await _repoAccountService.Get(repoId);
|
||||
Borrower primaryBorrower = repoAccount.Borrowers.Where((b) => b.OwnershipType == 6).FirstOrDefault();
|
||||
|
||||
if (primaryBorrower == null)
|
||||
{
|
||||
throw new NotFoundException($"Primary borrower for Repo Account {repoAccount.AccountNo} not found.");
|
||||
}
|
||||
|
||||
return primaryBorrower.IndividualId + '-' + refCollateralId;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will attempt to create an Auction through AutoIMS.
|
||||
*
|
||||
* It does not catch and will throw if anything goes wrong.
|
||||
* Assume lack of exception equals success.
|
||||
*/
|
||||
public async Task<WebServiceResponse> CreateAutoIMSAuction(
|
||||
Collateral collateral,
|
||||
VehicleLocation vehicleLocation,
|
||||
AuctionAssignment auctionAssignment)
|
||||
{
|
||||
|
||||
Vehicle vehicle = new Vehicle()
|
||||
{
|
||||
ClientAccountNumber = auctionAssignment.AuctionCustomerNo,
|
||||
VIN = collateral.Vin,
|
||||
Make = collateral.Make,
|
||||
Model = collateral.Model,
|
||||
ModelYear = collateral.Year.GetValueOrDefault(),
|
||||
BodyStyle = collateral.BodyStyle,
|
||||
Classification = collateral.VehicleType.GetValueOrDefault(),
|
||||
ClientField1 = auctionAssignment.ID + "",
|
||||
AuctionAssignment = new Shared.Collections.RepoApiLib.WebServices.AutoIMS.AuctionAssignment()
|
||||
{
|
||||
AuctionID = auctionAssignment.AuctionId,
|
||||
PickupDate = auctionAssignment.AvailablePickupDate.GetValueOrDefault().ToString("yyyy-MM-dd"),
|
||||
LegalSaleDate = auctionAssignment.LegalSaleDate.GetValueOrDefault().ToString("yyyy-MM-dd"),
|
||||
PickupOrDropOffNote = auctionAssignment.AuctionNotes,
|
||||
CleaningLevelRequested = auctionAssignment.CleaningLevel,
|
||||
},
|
||||
VehicleLocation = vehicleLocation,
|
||||
};
|
||||
|
||||
return await _autoImsWebService.PostVehicleAsync<Vehicle>(vehicle);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will attempt to remove an Auction through AutoIMS.
|
||||
*
|
||||
* It does not catch and will throw if anything goes wrong.
|
||||
* Assume lack of exception equals success.
|
||||
*/
|
||||
public async Task<WebServiceResponse> RemoveAuctionFromAutoIMS(AuctionAssignment auctionAssignment)
|
||||
{
|
||||
return await _autoImsWebService.RemoveFromAuction(auctionAssignment.AuctionCustomerNo, auctionAssignment.CollateralVIN);
|
||||
}
|
||||
|
||||
public IEnumerable<AuctionAssignmentToCollateral> CreateJoiningAuctionAssignmentToCollateralsList(AuctionAssignment auctionAssignment, IEnumerable<Collateral> collaterals)
|
||||
{
|
||||
return (from collateral in collaterals
|
||||
select new AuctionAssignmentToCollateral
|
||||
{
|
||||
AuctionAssignment = auctionAssignment,
|
||||
Collateral = collateral
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will attempt to call AutoIMS with an auction floor, and will not update the
|
||||
* floor related values unless it succeeds
|
||||
*/
|
||||
public async Task UpdateAuctionFloor(AuctionAssignment auctionAssignmentInput,
|
||||
AuctionAssignment existingAssignment)
|
||||
{
|
||||
FloorPriceVehicle vehicle = new FloorPriceVehicle();
|
||||
vehicle.ClientAccountNumber = auctionAssignmentInput.AuctionCustomerNo;
|
||||
vehicle.VIN = auctionAssignmentInput.CollateralVIN;
|
||||
vehicle.AuctionAssignment.FloorPrice = auctionAssignmentInput.FloorPrice ?? 0;
|
||||
|
||||
WebServiceResponse response = await _autoImsWebService.PostVehicleAsync<FloorPriceVehicle>(vehicle);
|
||||
|
||||
existingAssignment.FloorPriceResponseCode = response?.Status.Code;
|
||||
existingAssignment.FloorPriceResponseDescription = response?.Status.Description;
|
||||
|
||||
if (response?.Status.ValidationError?.Any() ?? false)
|
||||
{
|
||||
foreach (var item in response.Status.ValidationError)
|
||||
{
|
||||
existingAssignment.FloorPriceSubmissionErrors +=
|
||||
"Code: " + item.Code + " Message: " + item.Message + "|";
|
||||
}
|
||||
|
||||
}
|
||||
else if (response?.Status.Code == 1)
|
||||
{
|
||||
// e-mail management on successfully accepted floor price change
|
||||
var isManualFloorSet = existingAssignment.FloorPriceValuesUsed == auctionAssignmentInput.FloorPriceValuesUsed;
|
||||
var repoAccount = await _repoAccountService.FindByAuctionAssignmentId(existingAssignment.ID);
|
||||
await _emailer.SendFloorPriceAcceptedEmail(isManualFloorSet, repoAccount?.AccountNo, existingAssignment.FloorPrice, auctionAssignmentInput.FloorPrice);
|
||||
|
||||
existingAssignment.FloorPrice = auctionAssignmentInput.FloorPrice;
|
||||
existingAssignment.FloorPriceValuesUsed = auctionAssignmentInput.FloorPriceValuesUsed;
|
||||
}
|
||||
|
||||
await _completeAuctionAssignmentUpdateTransaction(existingAssignment);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will attempt to call AutoIMS and set the title sent date, and will not update the
|
||||
* title sent related values unless it succeeds
|
||||
*/
|
||||
public async Task UpdateTitleSentDate(AuctionAssignment auctionAssignmentInput,
|
||||
AuctionAssignment existingAssignment)
|
||||
{
|
||||
WebServiceResponse response = await _autoImsWebService.TitleSent(auctionAssignmentInput.CollateralVIN);
|
||||
|
||||
existingAssignment.TitleSentResponseCode = response?.Status.Code;
|
||||
existingAssignment.TitleSentResponseDescription = response?.Status.Description;
|
||||
|
||||
if (response?.Status.ValidationError?.Any() ?? false)
|
||||
{
|
||||
foreach (var item in response.Status.ValidationError)
|
||||
{
|
||||
existingAssignment.TitleSentSubmissionErrors +=
|
||||
"Code: " + item.Code + " Message: " + item.Message + "|";
|
||||
}
|
||||
|
||||
}
|
||||
else if (response?.Status.Code == 1)
|
||||
{
|
||||
existingAssignment.SentTitleDocsDate = auctionAssignmentInput.SentTitleDocsDate;
|
||||
}
|
||||
|
||||
await _completeAuctionAssignmentUpdateTransaction(existingAssignment);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will attempt to call AutoIMS and set the redeemed date, and will not update the
|
||||
* redeemed date unless it succeeds
|
||||
*/
|
||||
public async Task UpdateRedeemedDate(AuctionAssignment auctionAssignmentInput,
|
||||
AuctionAssignment existingAssignment)
|
||||
{
|
||||
WebServiceResponse response = await _autoImsWebService.Redeem(auctionAssignmentInput.CollateralVIN);
|
||||
|
||||
if (response?.Status.ValidationError?.Any() ?? false)
|
||||
{
|
||||
foreach (var item in response.Status.ValidationError)
|
||||
{
|
||||
// There are no database columns for redemption submission errors so log the error
|
||||
Log.Error($"Redemption submission error. Code: {item.Code} Message: {item.Message} | { auctionAssignmentInput}");
|
||||
}
|
||||
|
||||
}
|
||||
else if (response?.Status.Code == 1)
|
||||
{
|
||||
existingAssignment.RedeemedDate = auctionAssignmentInput.RedeemedDate;
|
||||
}
|
||||
|
||||
await _completeAuctionAssignmentUpdateTransaction(existingAssignment);
|
||||
}
|
||||
|
||||
/**
|
||||
* This function will attempt to call AutoIMS to place a hold, and will not update the
|
||||
* hold related values unless it succeeds
|
||||
*/
|
||||
public async Task UpdateHoldData(AuctionAssignment auctionAssignmentInput,
|
||||
AuctionAssignment existingAssignment)
|
||||
{
|
||||
var holdStatus = auctionAssignmentInput.HoldStatus ?? 0;
|
||||
var holdUntil = auctionAssignmentInput.HoldUntil ?? DateTime.MinValue;
|
||||
|
||||
WebServiceResponse response = await _autoImsWebService.PlaceHold(auctionAssignmentInput.CollateralVIN, holdStatus, holdUntil);
|
||||
|
||||
if (response?.Status.ValidationError?.Any() ?? false)
|
||||
{
|
||||
foreach (var item in response.Status.ValidationError)
|
||||
{
|
||||
existingAssignment.SubmissionErrors +=
|
||||
"Code: " + item.Code + " Message: " + item.Message + "|";
|
||||
}
|
||||
|
||||
}
|
||||
else if (response?.Status.Code == 1)
|
||||
{
|
||||
existingAssignment.HoldStatus = auctionAssignmentInput.HoldStatus;
|
||||
existingAssignment.HoldUntil = auctionAssignmentInput.HoldUntil;
|
||||
}
|
||||
|
||||
await _completeAuctionAssignmentUpdateTransaction(existingAssignment);
|
||||
}
|
||||
|
||||
public async Task<AuctionAssignment> RemoveAuction(int id)
|
||||
{
|
||||
var auctionAssignment = await _auctionAssignmentRepository.FindByIdAsync(id);
|
||||
|
||||
if (auctionAssignment == null)
|
||||
{
|
||||
throw new NotFoundException($" Auction Assignment with id {id} not found.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await RemoveAuctionFromAutoIMS(auctionAssignment);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failed to remove auction assignment from AutoIMS {auctionAssignment.ID}");
|
||||
throw new WebException("Failed to remove auction assignment from AutoIMS.");
|
||||
}
|
||||
|
||||
auctionAssignment.IsActive = false;
|
||||
await _completeAuctionAssignmentUpdateTransaction(auctionAssignment);
|
||||
|
||||
return auctionAssignment;
|
||||
}
|
||||
|
||||
private async Task _completeAuctionAssignmentUpdateTransaction(AuctionAssignment auctionAssignment)
|
||||
{
|
||||
try
|
||||
{
|
||||
_auctionAssignmentRepository.Update(auctionAssignment);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure updating Auction Assignment: {auctionAssignment}", auctionAssignment);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure updating Auction Assignment: {auctionAssignment}", auctionAssignment);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<IEnumerable<AuctionAssignment>> _findActiveAuctionsWithMatchingCollateral(int repoId, IEnumerable<Collateral> collaterals)
|
||||
{
|
||||
var assignments = await _auctionAssignmentRepository.FindByRepoIdAsync(repoId);
|
||||
if (assignments != null && assignments.Any())
|
||||
{
|
||||
return assignments.Where(auctionAssignment => auctionAssignment.IsActive == true &&
|
||||
auctionAssignment.AuctionAssignmentToCollaterals.Any(aa2c => collaterals
|
||||
.Select(collateral => collateral.CollateralId)
|
||||
.Contains(aa2c.CollateralId)));
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
+161
@@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlClient;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Exceptions;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Repositories;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public class AuctionHouseService : IAuctionHouseService
|
||||
{
|
||||
private readonly IAuctionHouseRepository _auctionHouseRepository;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
||||
public AuctionHouseService(
|
||||
IAuctionHouseRepository auctionHouseRepository,
|
||||
IUnitOfWork unitOfWork)
|
||||
{
|
||||
_auctionHouseRepository = auctionHouseRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<AuctionHouse>> ListAllAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _auctionHouseRepository.ListAllAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving Auctions");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AuctionHouse> GetAsync(string id)
|
||||
{
|
||||
AuctionHouse auction;
|
||||
try
|
||||
{
|
||||
auction = await _auctionHouseRepository.FindByIdAsync(id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving Auction with id: {id}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
|
||||
if (auction == null)
|
||||
{
|
||||
throw new NotFoundException($"Auction with id {id} not found.");
|
||||
}
|
||||
|
||||
return auction;
|
||||
}
|
||||
|
||||
public async Task<AuctionHouse> SaveAsync(AuctionHouse auction)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _auctionHouseRepository.AddAsync(auction);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
|
||||
return auction;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure saving Auction", auction);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AuctionHouse> UpdateAsync(AuctionHouse auction)
|
||||
{
|
||||
AuctionHouse existing;
|
||||
try
|
||||
{
|
||||
existing = await _auctionHouseRepository.FindByIdAsync(auction.AuctionID);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failed to find existing Auction House to update with id: {auction.AuctionID}", auction);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
|
||||
|
||||
if (existing == null)
|
||||
{
|
||||
throw new NotFoundException($"Auction with id {auction.AuctionID} not found.");
|
||||
}
|
||||
|
||||
existing.Name = auction.Name;
|
||||
existing.Active = auction.Active;
|
||||
existing.Address_Line_1 = auction.Address_Line_1;
|
||||
existing.Address_Line_2 = auction.Address_Line_2;
|
||||
existing.City = auction.City;
|
||||
existing.State = auction.State;
|
||||
existing.ZIP = auction.ZIP;
|
||||
existing.Country = auction.Country;
|
||||
existing.Phone = auction.Phone;
|
||||
existing.Fax = auction.Fax;
|
||||
existing.Latitude = auction.Latitude;
|
||||
existing.Longitude = auction.Longitude;
|
||||
|
||||
try
|
||||
{
|
||||
_auctionHouseRepository.Update(existing);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return (existing);
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure updating Auction: {auction}", auction);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure updating Auction {auction}", auction);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AuctionHouse> DeleteAsync(string id)
|
||||
{
|
||||
AuctionHouse existingAuction;
|
||||
try
|
||||
{
|
||||
existingAuction = await _auctionHouseRepository.FindByIdAsync(id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failed to find existing Auction House to delete with id: {id}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
|
||||
if (existingAuction == null)
|
||||
{
|
||||
throw new NotFoundException($"Auction with id {id} not found.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_auctionHouseRepository.Remove(existingAuction);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return existingAuction;
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure deleting Auction with id: {id}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure deleting Auction with id : {id}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
using System;
|
||||
using System.Data.SqlClient;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Exceptions;
|
||||
using LCU.API.Collections.RepoApi.Models.Filters;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Results;
|
||||
using LCU.Shared.Collections.RepoApiLib.Repositories;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public class AuditRecordService : IAuditRecordService
|
||||
{
|
||||
private readonly IAuditRecordRepository _repoAccountRepository;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
||||
public AuditRecordService(
|
||||
IAuditRecordRepository repoAccountRepository,
|
||||
IUnitOfWork unitOfWork)
|
||||
{
|
||||
_repoAccountRepository = repoAccountRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public async Task<PagedResult<AuditRecord>> ListAll(PageFilter filter)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _repoAccountRepository.ListAllAsync(filter.Page, filter.Limit);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving AuditRecords with page filter {filter}",filter);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AuditRecord> SaveAsync(AuditRecord repoAccount)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _repoAccountRepository.AddAsync(repoAccount);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
|
||||
return repoAccount;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure saving AuditRecord for repoAccount: {repoAccount}", repoAccount);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AuditRecord> Get(int id)
|
||||
{
|
||||
AuditRecord repoAccount;
|
||||
try
|
||||
{
|
||||
repoAccount = await _repoAccountRepository.FindByIdAsync(id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving AuditRecord wtih id: {id}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
|
||||
if (repoAccount == null)
|
||||
{
|
||||
throw new NotFoundException($" AuditRecords with id {id} not found.");
|
||||
}
|
||||
|
||||
return repoAccount;
|
||||
}
|
||||
|
||||
public async Task<AuditRecord> UpdateAsync(AuditRecord auditRecord)
|
||||
{
|
||||
|
||||
AuditRecord existingAuditRecord = null;
|
||||
|
||||
try
|
||||
{
|
||||
existingAuditRecord = await _repoAccountRepository.FindByIdAsync(auditRecord.ID);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retreving existing auditRecord wtih auditrecordId: AuditRecordID: {auditRecord.ID}", auditRecord);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
|
||||
if (existingAuditRecord == null)
|
||||
{
|
||||
throw new NotFoundException($" AuditRecord with id {auditRecord.ID} not found.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_repoAccountRepository.Update(existingAuditRecord);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return (existingAuditRecord);
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure updating AuditRecord: {auditRecord}", auditRecord);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure updating AuditRecord: {auditRecord}", auditRecord);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AuditRecord> DeleteAsync(int id)
|
||||
{
|
||||
AuditRecord existingAuditRecord = null;
|
||||
try
|
||||
{
|
||||
existingAuditRecord = await _repoAccountRepository.FindByIdAsync(id);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
|
||||
Log.Error(ex, $"Failure retreving existing auditRecord wtih auditrecordId: AuditRecordID: {id}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
|
||||
if (existingAuditRecord == null)
|
||||
{
|
||||
throw new NotFoundException($" AuditRecord with id {id} not found.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_repoAccountRepository.Remove(existingAuditRecord);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return existingAuditRecord;
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure deleting AuditRecord with id: {id}", id);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure deleting AuditRecord with id: {id}", id);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+94
@@ -0,0 +1,94 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using LCU.API.Collections.RepoApi.Configuration;
|
||||
using Serilog;
|
||||
using WinSCP;
|
||||
using SessionOptions = WinSCP.SessionOptions;
|
||||
using TransferMode = WinSCP.TransferMode;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.AutoIms
|
||||
{
|
||||
public class AuctionFtpClientService : IAuctionFtpClientService
|
||||
{
|
||||
private readonly SessionOptions _sessionOptions;
|
||||
private readonly RepoApiConfiguration _configuration;
|
||||
|
||||
public AuctionFtpClientService(RepoApiConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_sessionOptions = new SessionOptions()
|
||||
{
|
||||
Protocol = Protocol.Sftp,
|
||||
HostName = configuration.FtpConfiguration.HostName,
|
||||
UserName = configuration.FtpConfiguration.UserName,
|
||||
PortNumber = configuration.FtpConfiguration.Port,
|
||||
SshHostKeyFingerprint = configuration.FtpConfiguration.SshHostKeyFingerprint,
|
||||
SshPrivateKeyPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory,
|
||||
configuration.FtpConfiguration.SshPrivateKeyPath)
|
||||
};
|
||||
}
|
||||
|
||||
public Dictionary<FileInfo, string> Download()
|
||||
{
|
||||
var fileResults = new Dictionary<FileInfo, string>();
|
||||
using (Session session = new Session())
|
||||
{
|
||||
TransferOperationResult transferResult = null;
|
||||
try
|
||||
{
|
||||
session.ExecutablePath = _configuration.FtpConfiguration.ExecutablePath;
|
||||
session.Open(_sessionOptions);
|
||||
}
|
||||
catch (WinSCP.SessionLocalException wsle)
|
||||
{
|
||||
Log.Error(wsle, "Failed to open FTP session, WINSCP likely missing");
|
||||
return fileResults;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex,"Failed to open FTP session");
|
||||
return fileResults;
|
||||
}
|
||||
|
||||
TransferOptions transferOptions = new TransferOptions();
|
||||
transferOptions.TransferMode = TransferMode.Automatic;
|
||||
transferOptions.PreserveTimestamp = true;
|
||||
|
||||
RemoteDirectoryInfo directory = session.ListDirectory(_configuration.RemoteFilePath);
|
||||
|
||||
foreach (RemoteFileInfo fileInfo in directory.Files)
|
||||
{
|
||||
string localFileName = Path.Combine(_configuration.LocalDirectoryPath, fileInfo.Name);
|
||||
var remoteFileName = Path.Combine(_configuration.RemoteFilePath, fileInfo.Name);
|
||||
|
||||
FileInfo thisFileInfo = null;
|
||||
|
||||
try
|
||||
{
|
||||
if (!fileInfo.IsDirectory && !File.Exists(localFileName))
|
||||
{
|
||||
thisFileInfo = new FileInfo(localFileName);
|
||||
fileResults.Add(thisFileInfo, "success");
|
||||
|
||||
transferResult = session.GetFiles(remoteFileName, localFileName, true, transferOptions);
|
||||
|
||||
// Throw on any error
|
||||
transferResult.Check();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Exception while FTP'ing files ");
|
||||
if (thisFileInfo != null)
|
||||
{
|
||||
fileResults[thisFileInfo] = ex.Message;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fileResults;
|
||||
}
|
||||
}
|
||||
}
|
||||
+238
@@ -0,0 +1,238 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.Shared.Collections.RepoApiLib.Extensions;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Persistence;
|
||||
using LCU.Shared.Collections.RepoApiLib.Repositories;
|
||||
using LCU.Shared.Collections.RepoApiLib.WebServices.AutoIMS;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Serilog;
|
||||
using AuctionAssignment = LCU.Shared.Collections.RepoApiLib.Models.AuctionAssignment;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.AutoIms
|
||||
{
|
||||
public class AuctionProcessorDao : IAuctionProcessorDao
|
||||
{
|
||||
private readonly IImsAutoFileDataRepository _imsAutoFileDataRepository;
|
||||
private readonly IAutoImsWebService _autoImsWebService;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly AppDbContext _context;
|
||||
private readonly string statusTypeRepo = "REPO";
|
||||
private readonly string statusTypeSold = "SOLD";
|
||||
private readonly string statusTypeRedeemed = "REDEEMED";
|
||||
|
||||
public AuctionProcessorDao(
|
||||
IImsAutoFileDataRepository imsAutoFileDataRepository,
|
||||
IAutoImsWebService autoImsWebService,
|
||||
AppDbContext context,
|
||||
IUnitOfWork unitOfWork
|
||||
)
|
||||
{
|
||||
_context = context;
|
||||
_unitOfWork = unitOfWork;
|
||||
_imsAutoFileDataRepository = imsAutoFileDataRepository;
|
||||
_autoImsWebService = autoImsWebService;
|
||||
}
|
||||
|
||||
public string UpdateAutoImsFromAuctionData(ImsAutoFileData item)
|
||||
{
|
||||
try
|
||||
{
|
||||
WebServiceResponse response = null;
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
response = await _autoImsWebService.UpdateAuctionIdentifyingValuesAsync(
|
||||
item.AuctionCustomerNo,
|
||||
item.VIN,
|
||||
item.AuctionAssignmentId.ToString());
|
||||
}).Wait();
|
||||
return "Updated AutoIMS with Landmark identifiers.";
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "UpdateAutoIMS From Auction Failure");
|
||||
return "Failure Updating AutoIMS with Landmark identifiers.";
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetFormattedVin(string vin)
|
||||
{
|
||||
return !string.IsNullOrWhiteSpace(vin)
|
||||
? vin.Trim().PadLeft(17, '0').ToUpper()
|
||||
: null;
|
||||
}
|
||||
|
||||
public RepoAccount FindByAuctionCustomerNoAndVinAsync(string auctionCustomerNo, string unformattedVin)
|
||||
{
|
||||
List<RepoAccount> repos = FindRepoByStatus(auctionCustomerNo, unformattedVin, statusTypeRepo);
|
||||
|
||||
|
||||
if (!repos.Any())
|
||||
{
|
||||
repos = FindRepoByStatus(auctionCustomerNo, unformattedVin, statusTypeSold);
|
||||
}
|
||||
|
||||
if (!repos.Any())
|
||||
{
|
||||
repos = FindRepoByStatus(auctionCustomerNo, unformattedVin, statusTypeRedeemed);
|
||||
}
|
||||
|
||||
/*
|
||||
Currently we cannot error on multiple rows returned because we can have duplicate repos in the database
|
||||
We will need to perform extensive data cleanup before this can be enabled
|
||||
*/
|
||||
// if (repos.Count > 1)
|
||||
// {
|
||||
// throw new Exception($"Multiple Auction Assignments Returned For: AuctionCustomerNo:{auctionCustomerNo} | VIN:{vin}");
|
||||
// }
|
||||
|
||||
return repos.FirstOrDefault();
|
||||
}
|
||||
|
||||
public ImsAutoFileData UpdateExistingFile(ImsAutoFileData existingAuctionFile, ImsAutoFileData imsAutoFileData)
|
||||
{
|
||||
_context.Entry(existingAuctionFile).CurrentValues.SetValues(imsAutoFileData);
|
||||
imsAutoFileData.DateReceivedFromAutoIMS = existingAuctionFile.DateReceivedFromAutoIMS;
|
||||
imsAutoFileData.CollateralImagePath =
|
||||
_autoImsWebService.GetCollateralImage(imsAutoFileData.ConditionReportLink);
|
||||
_imsAutoFileDataRepository.Update(existingAuctionFile);
|
||||
_unitOfWork.CompleteAsync().GetAwaiter().GetResult();
|
||||
return imsAutoFileData;
|
||||
}
|
||||
|
||||
public ImsAutoFileData CreateImsAuctionFileData(ImsAutoFileData imsAutoFileData)
|
||||
{
|
||||
imsAutoFileData.DateReceivedFromAutoIMS = DateTime.Now;
|
||||
imsAutoFileData.CollateralImagePath =
|
||||
_autoImsWebService.GetCollateralImage(imsAutoFileData.ConditionReportLink);
|
||||
Add(imsAutoFileData);
|
||||
_unitOfWork.CompleteAsync().GetAwaiter().GetResult();
|
||||
|
||||
return imsAutoFileData;
|
||||
}
|
||||
|
||||
public void Add(ImsAutoFileData assignment)
|
||||
{
|
||||
_context.ImsAutoFileData.Add(assignment);
|
||||
}
|
||||
|
||||
public ImsAutoFileData FindByAuctionAssignmentIdAsync(int auctionAssignmentId)
|
||||
{
|
||||
return _context.ImsAutoFileData.AsNoTracking()
|
||||
.FirstOrDefault(x => x.AuctionAssignmentId == auctionAssignmentId);
|
||||
}
|
||||
|
||||
public ImsAutoFileData GetNonConformingImsFileRecord(string vin, string auctionCustomerNo)
|
||||
{
|
||||
ImsAutoFileData imsAutoFile = null;
|
||||
|
||||
// Allow for vehicle brought back to auction after sale. it happens!
|
||||
DateTime withinThirtyDays = DateTime.Now.AddDays(-30);
|
||||
imsAutoFile = _context.ImsAutoFileData.AsNoTracking().FirstOrDefault(x =>
|
||||
(x.VIN.Trim() == vin && x.AuctionCustomerNo == auctionCustomerNo
|
||||
&& (x.SoldDate == null || x.SoldDate >= withinThirtyDays)));
|
||||
|
||||
return imsAutoFile;
|
||||
}
|
||||
|
||||
|
||||
public ImsAutoFileData FindImsAuctionFileData(int auctionAssignmentId, string customerNo, string VIN)
|
||||
{
|
||||
var existingAuctionFile = FindByAuctionAssignmentIdAsync(auctionAssignmentId) ?? GetNonConformingImsFileRecord(VIN, customerNo);
|
||||
return existingAuctionFile;
|
||||
}
|
||||
|
||||
public AuctionAssignment FindByIdAsync(int id)
|
||||
{
|
||||
return _context.AuctionAssignments.Where(c => c.ID == id).IncludeAll().FirstOrDefault();
|
||||
}
|
||||
|
||||
public AuctionAssignment FindByAuctionCustomerNoAndVin(string auctionCustomerNo, string vin)
|
||||
{
|
||||
List<AuctionAssignment> auctionAssignments = FindAuctionByStatus(auctionCustomerNo, vin, statusTypeRepo);
|
||||
|
||||
if(!auctionAssignments.Any())
|
||||
{
|
||||
auctionAssignments = FindAuctionByStatus(auctionCustomerNo, vin, statusTypeSold);
|
||||
}
|
||||
|
||||
if (!auctionAssignments.Any())
|
||||
{
|
||||
auctionAssignments = FindAuctionByStatus(auctionCustomerNo, vin, statusTypeRedeemed);
|
||||
}
|
||||
|
||||
return auctionAssignments.FirstOrDefault();
|
||||
}
|
||||
|
||||
public AuctionAssignment FindAuctionAssignment(ImsAutoFileData item)
|
||||
{
|
||||
var auctionAssignment
|
||||
= /* FindByIdAsync(item.AuctionAssignmentId) this can't be used because old numbers will exist at first. ??*/
|
||||
FindByAuctionCustomerNoAndVin(item.AuctionCustomerNo, item.VIN);
|
||||
|
||||
if (auctionAssignment == null)
|
||||
{
|
||||
throw new Exception("Auction assignment not found for Individual/VIN " + item.AuctionCustomerNo);
|
||||
}
|
||||
|
||||
return auctionAssignment;
|
||||
}
|
||||
|
||||
public RepoAccount FindRepoForImsAuctionItem(ImsAutoFileData item)
|
||||
{
|
||||
RepoAccount repo = null;
|
||||
|
||||
// GJF, this can't be used because old numbers will exist at first....
|
||||
// if (item.AuctionAssignmentId > 0) {
|
||||
// repo = await _repoAccountRepository.FindByAuctionAssignmentIdAsync(item.AuctionAssignmentId);
|
||||
// }
|
||||
|
||||
repo = FindByAuctionCustomerNoAndVinAsync(item.AuctionCustomerNo, item.VIN);
|
||||
|
||||
if (repo == null)
|
||||
{
|
||||
throw new Exception("Repo Account not found for Individual/VIN " + item.AuctionCustomerNo);
|
||||
}
|
||||
|
||||
return repo;
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<AuctionAssignment> ListReadyForFloorPrice()
|
||||
{
|
||||
var temp = (from x in _context.AuctionAssignments
|
||||
where (x.FloorPrice ?? 0) == 0 && x.AutoIMS != null && x.AutoIMS.SoldDate == null &&
|
||||
(x.AutoIMS.StatedMileage ?? -1) > 1 && x.AutoIMS.ScheduledSaleDate != null &&
|
||||
x.AutoIMS.ScheduledSaleDate > DateTime.Now.Date &&
|
||||
x.RedeemedDate == null &&
|
||||
((x.AutoIMS.ApprovedVehicleGrade ?? 0) > 0 || (x.AutoIMS.VehicleGrade ?? 0) > 0)
|
||||
select x).OrderByDescending(x => x.ID);
|
||||
|
||||
return temp.IncludeAll().ToList();
|
||||
}
|
||||
|
||||
private List<RepoAccount> FindRepoByStatus(string auctionCustomerNo, string unformattedVin, string statusType)
|
||||
{
|
||||
return (from account in _context.RepoAccounts
|
||||
join collateral in _context.Collaterals on account.RepoId equals collateral.RepoId
|
||||
join auctionAssignment in _context.AuctionAssignments on account.RepoId equals auctionAssignment.RepoId
|
||||
where GetFormattedVin(collateral.Vin) == GetFormattedVin(unformattedVin)
|
||||
&& (auctionAssignment.AuctionCustomerNo == auctionCustomerNo || string.IsNullOrWhiteSpace(auctionCustomerNo))
|
||||
&& account.Status == statusType
|
||||
select account).ToList();
|
||||
}
|
||||
|
||||
private List<AuctionAssignment> FindAuctionByStatus(string auctionCustomerNo, string vin, string statusType)
|
||||
{
|
||||
return (from auctionAssignment in _context.AuctionAssignments
|
||||
join account in _context.RepoAccounts on auctionAssignment.RepoId equals account.RepoId
|
||||
join collateral in _context.Collaterals on account.RepoId equals collateral.RepoId
|
||||
where GetFormattedVin(collateral.Vin) == GetFormattedVin(vin)
|
||||
&& (auctionAssignment.AuctionCustomerNo == auctionCustomerNo || string.IsNullOrWhiteSpace(auctionCustomerNo))
|
||||
&& account.Status == statusType
|
||||
select auctionAssignment).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.AutoIms;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.AutoIms
|
||||
{
|
||||
public class AutoImsAuctionProcessorService : IAutoImsAuctionProcessorService
|
||||
{
|
||||
private readonly IAuctionProcessorDao _auctionProcessorDao;
|
||||
|
||||
public AutoImsAuctionProcessorService(IAuctionProcessorDao auctionProcessorDao)
|
||||
{
|
||||
_auctionProcessorDao = auctionProcessorDao;
|
||||
}
|
||||
|
||||
public ImportResultRow ImportSingleAutoImsAuction(ImsAutoFileData incomingAutoImsData)
|
||||
{
|
||||
AuctionAssignment auctionAssignment;
|
||||
var result = new ImportResultRow()
|
||||
{
|
||||
Success = true,
|
||||
Description = incomingAutoImsData.Description,
|
||||
Messages = new List<string>()
|
||||
};
|
||||
|
||||
incomingAutoImsData.VIN = incomingAutoImsData.VIN.Trim();
|
||||
if (incomingAutoImsData.VIN.Length != 17) // must be 17. Auction may add as less than 17 characters.
|
||||
{
|
||||
Log.Error($"Vin Length Not Correct, {incomingAutoImsData.AuctionCustomerNo}, {incomingAutoImsData.VIN}, Skipping.");
|
||||
result.Success = false;
|
||||
result.Messages.Add("Invalid VIN, this row has been skipped.");
|
||||
return result;
|
||||
}
|
||||
|
||||
bool requiresAutoImsUpdate =
|
||||
string.IsNullOrWhiteSpace(incomingAutoImsData.AuctionCustomerNo) ||
|
||||
incomingAutoImsData.AuctionAssignmentId == 0;
|
||||
|
||||
try
|
||||
{
|
||||
result.RepoAccount = _auctionProcessorDao.FindRepoForImsAuctionItem(incomingAutoImsData);
|
||||
auctionAssignment = _auctionProcessorDao.FindAuctionAssignment(incomingAutoImsData);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
result.Success = false;
|
||||
result.Messages.Add("Failed to load required data => " + ex.Message);
|
||||
return result;
|
||||
}
|
||||
|
||||
RepoAccount repo = result.RepoAccount;
|
||||
// Always re-assign these fields, carried from previous system.
|
||||
incomingAutoImsData.RepoId = auctionAssignment.RepoId ?? 0;
|
||||
incomingAutoImsData.AuctionCustomerNo = auctionAssignment.AuctionCustomerNo;
|
||||
incomingAutoImsData.AuctionAssignmentId = auctionAssignment.ID;
|
||||
|
||||
ImsAutoFileData existingAuctionFile = auctionAssignment.AutoIMS;
|
||||
if (existingAuctionFile == null)
|
||||
{
|
||||
existingAuctionFile = _auctionProcessorDao.FindImsAuctionFileData(auctionAssignment.ID,
|
||||
incomingAutoImsData.AuctionCustomerNo,
|
||||
incomingAutoImsData.VIN);
|
||||
}
|
||||
|
||||
if (existingAuctionFile == null)
|
||||
{
|
||||
result.Messages.Add("New Entry Created.");
|
||||
existingAuctionFile = _auctionProcessorDao.CreateImsAuctionFileData(incomingAutoImsData);
|
||||
if (existingAuctionFile.AssignmentAccepted != null)
|
||||
{
|
||||
result.Messages.Add("Auction accepted by Auto IMS on " + existingAuctionFile.AssignmentAccepted);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Messages.Add("Updated existing IMS Auction Entry.");
|
||||
existingAuctionFile = _auctionProcessorDao.UpdateExistingFile(existingAuctionFile, incomingAutoImsData);
|
||||
}
|
||||
|
||||
result = CheckAuctionGates(existingAuctionFile, result);
|
||||
|
||||
// update autoims with customer number and auction assignment id when not added through our interface.
|
||||
if (requiresAutoImsUpdate)
|
||||
{
|
||||
result.Messages.Add(
|
||||
_auctionProcessorDao.UpdateAutoImsFromAuctionData(existingAuctionFile));
|
||||
}
|
||||
|
||||
result.Data = existingAuctionFile;
|
||||
return result;
|
||||
}
|
||||
|
||||
public ImportResultRow CheckAuctionGates(ImsAutoFileData existingAuctionFile, ImportResultRow result)
|
||||
{
|
||||
if (existingAuctionFile.VehicleGrade > 0)
|
||||
{
|
||||
result.Messages.Add("Grade present: " + existingAuctionFile.VehicleGrade + ".");
|
||||
}
|
||||
|
||||
if (existingAuctionFile.FloorPriceAccepted > 0)
|
||||
{
|
||||
result.Messages.Add("Accepted Floor Price present: $" +
|
||||
existingAuctionFile.FloorPriceAccepted + ".");
|
||||
}
|
||||
|
||||
if (existingAuctionFile.SoldDate != null)
|
||||
{
|
||||
result.Messages.Add("Auction Sold:" + existingAuctionFile.SoldDate + ".");
|
||||
}
|
||||
|
||||
if (existingAuctionFile.FundsSent != null)
|
||||
{
|
||||
result.Messages.Add("Funds Send Date received: " + existingAuctionFile.FundsSent + ".");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using LCU.API.Collections.RepoApi.Configuration;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.AutoIms
|
||||
{
|
||||
public class AutoImsDownloaderService : IAutoImsDownloaderService
|
||||
{
|
||||
private readonly RepoApiConfiguration _configuration;
|
||||
private readonly IAuctionFtpClientService _auctionFtpClientService;
|
||||
|
||||
public AutoImsDownloaderService(IAuctionFtpClientService auctionFtpClientService,
|
||||
RepoApiConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
_auctionFtpClientService = auctionFtpClientService;
|
||||
}
|
||||
|
||||
public Dictionary<FileInfo, string> DownLoadAuctionFiles()
|
||||
{
|
||||
var fileResults = _auctionFtpClientService.Download();
|
||||
return fileResults;
|
||||
}
|
||||
|
||||
public Dictionary<FileInfo, string> CombineWithLocalFiles(Dictionary<FileInfo, string> fileResults)
|
||||
{
|
||||
// check if there are any files in the folder already that are not in our downloaded list.
|
||||
string[] fileEntries = Directory.GetFiles(_configuration.LocalDirectoryPath);
|
||||
|
||||
foreach (string file in fileEntries)
|
||||
{
|
||||
var newFile = new FileInfo(file);
|
||||
Log.Information($"Auto IMS Job Added file: {file}");
|
||||
|
||||
if (fileResults.Keys.All(x => x.Name != newFile.Name))
|
||||
{
|
||||
fileResults.Add(newFile, "success");
|
||||
}
|
||||
}
|
||||
|
||||
return fileResults;
|
||||
}
|
||||
|
||||
public IEnumerable<FileInfo> ConvertToFileList(Dictionary<FileInfo, string> fileResults)
|
||||
{
|
||||
if (fileResults.Any(x => x.Value != "success"))
|
||||
{
|
||||
foreach (var file in fileResults.Where(x => x.Value != "success"))
|
||||
{
|
||||
Log.Information($"File Downloads failed: {file.Key} - {file.Value}");
|
||||
}
|
||||
}
|
||||
|
||||
if (fileResults.Any(x => x.Value == "success"))
|
||||
{
|
||||
var ascendingOrderOfFiles = fileResults.Where(x => x.Value == "success")
|
||||
.OrderBy(x => x.Key.LastWriteTime).Select(x => x.Key).ToList();
|
||||
return ascendingOrderOfFiles;
|
||||
}
|
||||
|
||||
Log.Information("No files found from AutoIMS.");
|
||||
return new List<FileInfo>();
|
||||
}
|
||||
}
|
||||
}
|
||||
+133
@@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.AutoIms;
|
||||
using LCU.Shared.Collections.RepoApiLib.Repositories;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.AutoIms
|
||||
{
|
||||
public class AutoImsImporterService : IAutoImsImporterService
|
||||
{
|
||||
private readonly IAuctionAssignmentRepository _auctionAssignmentRepository;
|
||||
private readonly IAutoImsNoteProcessorService _autoImsNoteProcessorService;
|
||||
private readonly IAutoImsAuctionProcessorService _autoImsAuctionProcessorService;
|
||||
private readonly IFloorPricingService _floorPricingService;
|
||||
private readonly IAuctionAssignmentService _auctionAssignmentService;
|
||||
|
||||
public AutoImsImporterService(
|
||||
IAuctionAssignmentRepository auctionAssignmentRepository,
|
||||
IAutoImsNoteProcessorService autoImsNoteProcessorService,
|
||||
IAutoImsAuctionProcessorService autoImsAuctionProcessorService, IFloorPricingService floorPricingService,
|
||||
IAuctionAssignmentService auctionAssignmentService)
|
||||
{
|
||||
_autoImsAuctionProcessorService = autoImsAuctionProcessorService;
|
||||
_floorPricingService = floorPricingService;
|
||||
_auctionAssignmentService = auctionAssignmentService;
|
||||
_autoImsNoteProcessorService = autoImsNoteProcessorService;
|
||||
_auctionAssignmentRepository = auctionAssignmentRepository;
|
||||
}
|
||||
|
||||
/**
|
||||
* A set of notes comes from AutoIMS and must be inserted (But not updated).
|
||||
* Duplicates/Existing are checked for based on writer, time and text.
|
||||
*/
|
||||
public IReadOnlyCollection<AuctionAssignmentNote> ImportNotesToDatabase(IEnumerable<AuctionAssignmentNote> data)
|
||||
{
|
||||
List<AuctionAssignmentNote> savedData = new List<AuctionAssignmentNote>();
|
||||
foreach (var item in data)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = _autoImsNoteProcessorService.ImportSingleNoteRow(item);
|
||||
if (result != null)
|
||||
{
|
||||
savedData.Add(result);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Error processing notes for auction accountNo: {item.AuctionCustomerNo}");
|
||||
}
|
||||
}
|
||||
|
||||
return savedData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Each row in the Data file is an ImsAutoAuctionData row used to track the status of
|
||||
* a Repo at auction and assist in setting the floor.
|
||||
*/
|
||||
public IReadOnlyCollection<ImportResultRow> ImportDataFile(IEnumerable<ImsAutoFileData> models)
|
||||
{
|
||||
List<ImportResultRow> savedData = new List<ImportResultRow>();
|
||||
foreach (var item in models)
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = _autoImsAuctionProcessorService.ImportSingleAutoImsAuction(item);
|
||||
if (result != null)
|
||||
{
|
||||
savedData.Add(result);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Error processing data for auction account with repoId: {item.RepoId}");
|
||||
}
|
||||
}
|
||||
|
||||
return savedData;
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<ImportResultRow> SetAllAvailableFloors()
|
||||
{
|
||||
var list = new List<ImportResultRow>();
|
||||
var auctionAssignments =
|
||||
_auctionAssignmentRepository.ListReadyForFloorPriceAsync()
|
||||
.ConfigureAwait(continueOnCapturedContext: false).GetAwaiter().GetResult();
|
||||
|
||||
if (auctionAssignments.Items != null && auctionAssignments.Items.Any())
|
||||
{
|
||||
foreach (var auctionAssignment in auctionAssignments.Items)
|
||||
{
|
||||
var result = new ImportResultRow() {Messages = new List<string>()};
|
||||
var floorPriceCalculationResultSet = _floorPricingService.DetermineFloorPrice(auctionAssignment.ID);
|
||||
|
||||
if (floorPriceCalculationResultSet?.PrimaryResult != null)
|
||||
{
|
||||
result.Messages.Add("Floor Price Results Generated for Account " + auctionAssignment.RepoId);
|
||||
if (floorPriceCalculationResultSet.PrimaryResult.Success)
|
||||
{
|
||||
result.Messages.Add("Floor Price Results SUCCESS for Account " + auctionAssignment.RepoId);
|
||||
auctionAssignment.FloorPrice = floorPriceCalculationResultSet.PrimaryResult.FloorPrice;
|
||||
auctionAssignment.FloorPriceValuesUsed =
|
||||
floorPriceCalculationResultSet.FloorPriceValuesUsed;
|
||||
var updated = _auctionAssignmentService.UpdateAsync(auctionAssignment).GetAwaiter()
|
||||
.GetResult();
|
||||
Log.Information($"Auction Floor Set Id:{updated.ID}, FloorPrice:{updated.FloorPrice}, Skipping.");
|
||||
result.Messages.Add(
|
||||
$"Floor price set for {auctionAssignment.RepoId} to {auctionAssignment.FloorPrice}");
|
||||
result.Success = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Messages.Add("Floor Price Results FAILURE for Account " + auctionAssignment.RepoId);
|
||||
result.Messages.Add("Reason: " +
|
||||
floorPriceCalculationResultSet.PrimaryResult.FailureReason);
|
||||
}
|
||||
}
|
||||
|
||||
list.Add(result);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Information("SetAllAvailableFloors: No floors to set.");
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Repositories;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.AutoIms
|
||||
{
|
||||
public class AutoImsNoteProcessorService : IAutoImsNoteProcessorService
|
||||
{
|
||||
private readonly IAuctionAssignmentNoteRepository _auctionAssignmentNoteRepository;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
||||
public AutoImsNoteProcessorService(IAuctionAssignmentNoteRepository auctionAssignmentNoteRepository,
|
||||
IUnitOfWork unitOfWork
|
||||
)
|
||||
{
|
||||
_unitOfWork = unitOfWork;
|
||||
_auctionAssignmentNoteRepository = auctionAssignmentNoteRepository;
|
||||
}
|
||||
|
||||
public AuctionAssignmentNote ImportSingleNoteRow(AuctionAssignmentNote item)
|
||||
{
|
||||
var dateWritten = item.DateWritten ?? DateTime.Now;
|
||||
var existing =
|
||||
_auctionAssignmentNoteRepository.FindConflictingNotes(dateWritten, item.Text, item.Writer)
|
||||
.GetAwaiter().GetResult();
|
||||
|
||||
if (!existing.Any())
|
||||
{
|
||||
item.DateWritten = dateWritten;
|
||||
Log.Information("Saving Note.");
|
||||
_auctionAssignmentNoteRepository.AddAsync(item);
|
||||
_unitOfWork.CompleteAsync().GetAwaiter().GetResult();
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Information($"Note already exists, customerNo: {item.AuctionCustomerNo} on writeDate: {item.DateWritten}");
|
||||
}
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.AutoIms
|
||||
{
|
||||
public interface IAuctionFtpClientService
|
||||
{
|
||||
Dictionary<FileInfo, string> Download();
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.AutoIms
|
||||
{
|
||||
public interface IAuctionProcessorDao
|
||||
{
|
||||
string UpdateAutoImsFromAuctionData(ImsAutoFileData item);
|
||||
|
||||
RepoAccount FindByAuctionCustomerNoAndVinAsync(string auctionCustomerNo,
|
||||
string unformattedVin);
|
||||
|
||||
ImsAutoFileData UpdateExistingFile(ImsAutoFileData existingAuctionFile, ImsAutoFileData imsAutoFileData);
|
||||
ImsAutoFileData CreateImsAuctionFileData(ImsAutoFileData imsAutoFileData);
|
||||
void Add(ImsAutoFileData assignment);
|
||||
ImsAutoFileData FindByAuctionAssignmentIdAsync(int auctionAssignmentId);
|
||||
ImsAutoFileData GetNonConformingImsFileRecord(string vin, string auctionCustomerNo);
|
||||
ImsAutoFileData FindImsAuctionFileData(int auctionAssignmentId, string customerNo, string VIN);
|
||||
AuctionAssignment FindByIdAsync(int id);
|
||||
AuctionAssignment FindByAuctionCustomerNoAndVin(string auctionCustomerNo, string vin);
|
||||
AuctionAssignment FindAuctionAssignment(ImsAutoFileData item);
|
||||
RepoAccount FindRepoForImsAuctionItem(ImsAutoFileData item);
|
||||
IReadOnlyCollection<AuctionAssignment> ListReadyForFloorPrice();
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.AutoIms;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.AutoIms
|
||||
{
|
||||
public interface IAutoImsAuctionProcessorService
|
||||
{
|
||||
ImportResultRow ImportSingleAutoImsAuction(ImsAutoFileData item);
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.AutoIms
|
||||
{
|
||||
public interface IAutoImsDownloaderService
|
||||
{
|
||||
Dictionary<FileInfo, string> DownLoadAuctionFiles();
|
||||
Dictionary<FileInfo, string> CombineWithLocalFiles(Dictionary<FileInfo, string> fileResults);
|
||||
IEnumerable<FileInfo> ConvertToFileList(Dictionary<FileInfo, string> fileResults);
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.AutoIms;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.AutoIms
|
||||
{
|
||||
public interface IAutoImsImporterService
|
||||
{
|
||||
IReadOnlyCollection<AuctionAssignmentNote> ImportNotesToDatabase(IEnumerable<AuctionAssignmentNote> data);
|
||||
IReadOnlyCollection<ImportResultRow> ImportDataFile(IEnumerable<ImsAutoFileData> models);
|
||||
IReadOnlyCollection<ImportResultRow> SetAllAvailableFloors();
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.AutoIms
|
||||
{
|
||||
public interface IAutoImsNoteProcessorService
|
||||
{
|
||||
AuctionAssignmentNote ImportSingleNoteRow(AuctionAssignmentNote item);
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.AutoIms
|
||||
{
|
||||
public interface IImsAutoFileDataConverter
|
||||
{
|
||||
ImsAutoFileData SetFieldByName(ImsAutoFileData input, string fieldName, string value);
|
||||
IReadOnlyCollection<ImsAutoFileData> CsvToImsAutoFileData(Stream file);
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.AutoIms
|
||||
{
|
||||
public interface IImsAutoFileNoteConverter
|
||||
{
|
||||
IEnumerable<AuctionAssignmentNote> CsvToImsAutoFileNotes(Stream file);
|
||||
IEnumerable<AuctionAssignmentNote> CompressIdentifyingRows(IEnumerable<AuctionAssignmentNote> csvNotes);
|
||||
AuctionAssignmentNote SetFieldByName(AuctionAssignmentNote input, string fieldName, string value);
|
||||
}
|
||||
}
|
||||
+311
@@ -0,0 +1,311 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using LCU.API.Collections.RepoApi.Configuration;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using NotVisualBasic.FileIO;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.AutoIms
|
||||
{
|
||||
public class ImsAutoFileDataConverter : IImsAutoFileDataConverter
|
||||
{
|
||||
private readonly RepoApiConfiguration _configuration;
|
||||
|
||||
public ImsAutoFileDataConverter( RepoApiConfiguration configuration)
|
||||
{
|
||||
_configuration = configuration;
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<ImsAutoFileData> CsvToImsAutoFileData(Stream file)
|
||||
{
|
||||
List<ImsAutoFileData> listOfObjects = new List<ImsAutoFileData>();
|
||||
using (CsvTextFieldParser tfp = new CsvTextFieldParser(file))
|
||||
{
|
||||
// Get Some Column Names
|
||||
string[] columns = tfp.ReadFields();
|
||||
|
||||
while (!tfp.EndOfData)
|
||||
{
|
||||
var fields = tfp.ReadFields();
|
||||
|
||||
if (!fields[0].Contains("------") && (fields.Any(x => x.Trim().Length > 0)))
|
||||
{
|
||||
var obj = new ImsAutoFileData();
|
||||
|
||||
for (var i = 0; i < fields.Length; i++)
|
||||
{
|
||||
obj = SetFieldByName(obj, columns[i].Trim(), fields[i]);
|
||||
}
|
||||
|
||||
listOfObjects.Add(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
return listOfObjects;
|
||||
}
|
||||
|
||||
public ImsAutoFileData SetFieldByName(ImsAutoFileData input, string fieldName, string value)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (fieldName)
|
||||
{
|
||||
case "Announced Conditions":
|
||||
input.AnnouncedConditions = value.Trim();
|
||||
break;
|
||||
case "Arbitration Reason":
|
||||
input.ArbitrationReason = value.Trim();
|
||||
break;
|
||||
case "As Is 100K Miles":
|
||||
input.AsIs100KMiles = (value.Trim() == "Yes");
|
||||
break;
|
||||
case "Assignment Accepted":
|
||||
input.AssignmentAccepted =
|
||||
!string.IsNullOrWhiteSpace(value) ? DateTime.Parse(value) : (DateTime?) null;
|
||||
break;
|
||||
case "Auction Age":
|
||||
input.AuctionAge = !string.IsNullOrWhiteSpace(value) ? NumbersOnly<int>(value) : (int?) null;
|
||||
break;
|
||||
case "Auction Guide Book Price":
|
||||
input.AuctionGuideBookPrice = !string.IsNullOrWhiteSpace(value)
|
||||
? NumbersOnly<decimal>(value)
|
||||
: (decimal?) null;
|
||||
break;
|
||||
case "Buyer ID":
|
||||
input.BuyerId = value.Trim();
|
||||
break;
|
||||
case "Buyer Name":
|
||||
input.BuyerName = value.Trim();
|
||||
break;
|
||||
case "Charge Amount":
|
||||
input.ChargeAmount = !string.IsNullOrWhiteSpace(value)
|
||||
? NumbersOnly<decimal>(value)
|
||||
: (decimal?) null;
|
||||
break;
|
||||
case "Check Amount":
|
||||
input.CheckAmount = !string.IsNullOrWhiteSpace(value)
|
||||
? NumbersOnly<decimal>(value)
|
||||
: (decimal?) null;
|
||||
break;
|
||||
case "Client Age":
|
||||
input.ClientAge = !string.IsNullOrWhiteSpace(value) ? NumbersOnly<int>(value) : (int?) null;
|
||||
break;
|
||||
case "Client Cancelled":
|
||||
input.ClientCancelled =
|
||||
!string.IsNullOrWhiteSpace(value) ? DateTime.Parse(value) : (DateTime?) null;
|
||||
break;
|
||||
case "Collision Damage":
|
||||
input.CollisionDamage = value.Trim();
|
||||
break;
|
||||
case "Inspected Date":
|
||||
input.InspectedDate = !string.IsNullOrEmpty(value) ? DateTime.Parse(value) : (DateTime?) null;
|
||||
break;
|
||||
case "Date of Loss":
|
||||
input.DateofLoss = !string.IsNullOrEmpty(value) ? DateTime.Parse(value) : (DateTime?) null;
|
||||
break;
|
||||
case "Date Picked Up":
|
||||
input.DatePickedUp = !string.IsNullOrEmpty(value) ? DateTime.Parse(value) : (DateTime?) null;
|
||||
break;
|
||||
case "Date Entered AutoIMS":
|
||||
input.DateEnteredAutoIMS = !string.IsNullOrEmpty(value)
|
||||
? DateTime.Parse(value.Substring(0, value.Trim().Length - 4))
|
||||
: (DateTime?) null;
|
||||
break;
|
||||
case "Days Run":
|
||||
input.DaysRun = !string.IsNullOrWhiteSpace(value) ? NumbersOnly<int>(value) : (int?) null;
|
||||
break;
|
||||
case "Days Run/Highest Bid Recorded by Auction":
|
||||
input.DaysRunHighestBidRecordedbyAuction = value;
|
||||
break;
|
||||
case "Days to Delivery":
|
||||
input.DaystoDelivery =
|
||||
!string.IsNullOrWhiteSpace(value) ? NumbersOnly<int>(value) : (int?) null;
|
||||
break;
|
||||
case "Description":
|
||||
input.Description = value.Trim();
|
||||
break;
|
||||
case "Drivable":
|
||||
input.IsDrivable = !string.IsNullOrWhiteSpace(value)
|
||||
? value == "Yes" ? true : false
|
||||
: (bool?) null;
|
||||
break;
|
||||
case "Floor Price (Accepted)":
|
||||
input.FloorPriceAccepted = !string.IsNullOrWhiteSpace(value)
|
||||
? NumbersOnly<decimal>(value)
|
||||
: (decimal?) null;
|
||||
break;
|
||||
case "Funds Sent":
|
||||
input.FundsSent = !string.IsNullOrEmpty(value) ? DateTime.Parse(value) : (DateTime?) null;
|
||||
break;
|
||||
case "Gross % Floor":
|
||||
input.GrossPercentFloor =
|
||||
!string.IsNullOrWhiteSpace(value) ? NumbersOnly<decimal>(value) : (decimal?) null;
|
||||
break;
|
||||
case "Highest Bid":
|
||||
input.HighestBid = !string.IsNullOrWhiteSpace(value)
|
||||
? NumbersOnly<decimal>(value)
|
||||
: (decimal?) null;
|
||||
break;
|
||||
case "Mileage":
|
||||
input.StatedMileage = !string.IsNullOrWhiteSpace(value)
|
||||
? int.Parse(value.Replace(",", ""))
|
||||
: (int?) null;
|
||||
break;
|
||||
case "Net % Floor":
|
||||
input.NetPercentFloor = !string.IsNullOrWhiteSpace(value)
|
||||
? NumbersOnly<decimal>(value)
|
||||
: (decimal?) null;
|
||||
break;
|
||||
case "Other Advance Fees":
|
||||
input.OtherAdvanceFees = !string.IsNullOrWhiteSpace(value)
|
||||
? NumbersOnly<decimal>(value)
|
||||
: (decimal?) null;
|
||||
break;
|
||||
case "OTHER Total":
|
||||
input.OTHERTotal = value.Trim();
|
||||
break;
|
||||
case "OTHER: Advanced Other(72)":
|
||||
input.OTHERAdvancedOther = value.Trim();
|
||||
break;
|
||||
case "OTHER: Advanced Storage(71)":
|
||||
input.OTHERAdvancedStorage = value.Trim();
|
||||
break;
|
||||
case "OTHER: Advanced Tow(70)":
|
||||
input.OTHERAdvancedTow = value.Trim();
|
||||
break;
|
||||
case "OTHER: Miscellaneous(41)":
|
||||
input.OTHERMiscellaneous = value.Trim();
|
||||
break;
|
||||
case "Sale Price":
|
||||
input.SalePrice = !string.IsNullOrWhiteSpace(value)
|
||||
? NumbersOnly<decimal>(value)
|
||||
: (decimal?) null;
|
||||
break;
|
||||
case "Scheduled Sale Date":
|
||||
input.ScheduledSaleDate =
|
||||
!string.IsNullOrWhiteSpace(value) ? DateTime.Parse(value) : (DateTime?) null;
|
||||
break;
|
||||
case "Sold Date":
|
||||
input.SoldDate = !string.IsNullOrWhiteSpace(value) ? DateTime.Parse(value) : (DateTime?) null;
|
||||
break;
|
||||
case "Title #":
|
||||
input.TitleNumber = value.Trim();
|
||||
break;
|
||||
case "Title Received":
|
||||
input.TitleReceived =
|
||||
!string.IsNullOrWhiteSpace(value) ? DateTime.Parse(value) : (DateTime?) null;
|
||||
break;
|
||||
case "Title Returned":
|
||||
input.TitleReturned =
|
||||
!string.IsNullOrWhiteSpace(value) ? DateTime.Parse(value) : (DateTime?) null;
|
||||
break;
|
||||
case "Title State":
|
||||
input.TitleState = value.Trim();
|
||||
break;
|
||||
case "Title Status":
|
||||
input.TitleStatus = value.Trim();
|
||||
break;
|
||||
case "Title Status from Auction":
|
||||
input.TitleStatusfromAuction = value.Trim();
|
||||
break;
|
||||
case "Total Charges":
|
||||
input.TotalCharges = !string.IsNullOrWhiteSpace(value)
|
||||
? NumbersOnly<decimal>(value)
|
||||
: (decimal?) null;
|
||||
break;
|
||||
case "Vehicle Grade (Approved CR)":
|
||||
input.ApprovedVehicleGrade = !string.IsNullOrWhiteSpace(value)
|
||||
? NumbersOnly<decimal>(value)
|
||||
: (decimal?) null;
|
||||
break;
|
||||
case "Operable":
|
||||
input.IsOperable = !string.IsNullOrWhiteSpace(value)
|
||||
? value == "Yes" ? true : false
|
||||
: (bool?) null;
|
||||
break;
|
||||
case "Shipment Date":
|
||||
input.ShipmentDate =
|
||||
!string.IsNullOrWhiteSpace(value) ? DateTime.Parse(value) : (DateTime?) null;
|
||||
break;
|
||||
case "Status":
|
||||
input.Status = value.Trim();
|
||||
break;
|
||||
case "VIN":
|
||||
input.VIN = value.Trim();
|
||||
break;
|
||||
case "Storage Cost":
|
||||
input.StorageCost = !string.IsNullOrWhiteSpace(value)
|
||||
? NumbersOnly<decimal>(value)
|
||||
: (decimal?) null;
|
||||
break;
|
||||
case "Storage Rate Per Day":
|
||||
input.StorageRatePerDay = !string.IsNullOrWhiteSpace(value)
|
||||
? NumbersOnly<decimal>(value)
|
||||
: (decimal?) null;
|
||||
break;
|
||||
case "Tow Cost":
|
||||
input.TowCost = !string.IsNullOrWhiteSpace(value)
|
||||
? NumbersOnly<decimal>(value)
|
||||
: (decimal?) null;
|
||||
break;
|
||||
case "Cn. Acc. #":
|
||||
input.AuctionCustomerNo = value.Trim();
|
||||
break;
|
||||
case "Auction ID":
|
||||
break;
|
||||
case "Vehicle Decimal Grade Code":
|
||||
input.VehicleDecimalGradeCode = !string.IsNullOrWhiteSpace(value)
|
||||
? NumbersOnly<decimal>(value)
|
||||
: (decimal?) null;
|
||||
break;
|
||||
case "Vehicle Grade":
|
||||
input.VehicleGrade = !string.IsNullOrWhiteSpace(value)
|
||||
? NumbersOnly<decimal>(value)
|
||||
: (decimal?) null;
|
||||
break;
|
||||
case "Most Recent National Adjusted MMR Price":
|
||||
input.MMRNationalAdjustedPrice = !string.IsNullOrWhiteSpace(value)
|
||||
? NumbersOnly<decimal>(value)
|
||||
: (decimal?) null;
|
||||
break;
|
||||
case "Most Recent Regional Adjusted MMR Price":
|
||||
input.MMRRegionalAdjustedPrice = !string.IsNullOrWhiteSpace(value)
|
||||
? NumbersOnly<decimal>(value)
|
||||
: (decimal?) null;
|
||||
break;
|
||||
case "MMR":
|
||||
input.MMR = value.Trim();
|
||||
break;
|
||||
case "External CR Link":
|
||||
input.ConditionReportLink = _configuration.ConditionReportUrl + value;
|
||||
break;
|
||||
case "Open Text 1":
|
||||
input.AuctionAssignmentId =
|
||||
!string.IsNullOrWhiteSpace(value) ? int.Parse(value.Replace(",", "")) : -1;
|
||||
break;
|
||||
default:
|
||||
Log.Error("Got unknown field {0} with value {1} for ImsAutoFileData", fieldName,
|
||||
value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failed to set field {fieldName} with value {value} for ImsAutoFileData");
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
|
||||
private static T NumbersOnly<T>(string val)
|
||||
{
|
||||
var formattedStr = Regex.Replace(val.Trim(), "[^0-9\\.]", "");
|
||||
if (string.IsNullOrWhiteSpace(formattedStr))
|
||||
formattedStr = "-1";
|
||||
return (T) Convert.ChangeType(formattedStr, typeof(T));
|
||||
}
|
||||
}
|
||||
}
|
||||
+129
@@ -0,0 +1,129 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using NotVisualBasic.FileIO;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.AutoIms
|
||||
{
|
||||
public class ImsAutoFileNoteConverter : IImsAutoFileNoteConverter
|
||||
{
|
||||
public ImsAutoFileNoteConverter()
|
||||
{
|
||||
}
|
||||
|
||||
public IEnumerable<AuctionAssignmentNote> CsvToImsAutoFileNotes(Stream file)
|
||||
{
|
||||
List<AuctionAssignmentNote> listOfObjects = new List<AuctionAssignmentNote>();
|
||||
|
||||
using (CsvTextFieldParser tfp = new CsvTextFieldParser(file))
|
||||
{
|
||||
// Get Some Column Names
|
||||
string[] columns = tfp.ReadFields();
|
||||
|
||||
while (!tfp.EndOfData)
|
||||
{
|
||||
var fields = tfp.ReadFields();
|
||||
|
||||
if (!fields[0].Contains("------") && (fields.Any(x => x.Trim().Length > 0)))
|
||||
{
|
||||
var obj = new AuctionAssignmentNote();
|
||||
|
||||
for (var i = 0; i < fields.Length; i++)
|
||||
{
|
||||
obj = SetFieldByName(obj, columns[i].Trim(), fields[i].Trim());
|
||||
}
|
||||
|
||||
listOfObjects.Add(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return listOfObjects;
|
||||
}
|
||||
|
||||
/**
|
||||
* Take the rows with only account and discard them, using the account no to update the other rows.
|
||||
* Each row in the CSV does not have CustomerAccountNo, only the first row of each set.
|
||||
* Further, if a row has a customer account no, it has nothing else.
|
||||
*/
|
||||
public IEnumerable<AuctionAssignmentNote> CompressIdentifyingRows(IEnumerable<AuctionAssignmentNote> csvNotes)
|
||||
{
|
||||
List<AuctionAssignmentNote> data = new List<AuctionAssignmentNote>();
|
||||
string accountNo = String.Empty;
|
||||
|
||||
foreach (var item in csvNotes)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(item.AuctionCustomerNo) && item.AuctionCustomerNo != accountNo)
|
||||
{
|
||||
accountNo = item.AuctionCustomerNo;
|
||||
// Skip account rows, unless they start showing up with data.
|
||||
if (string.IsNullOrWhiteSpace(item.Text))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(accountNo))
|
||||
{
|
||||
Log.Error($"No Account Number when processing notes row, {item.Text}");
|
||||
return null;
|
||||
}
|
||||
|
||||
item.AuctionCustomerNo = accountNo;
|
||||
data.Add(item);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Error compressing notes for auction accountNo: {accountNo}");
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
public AuctionAssignmentNote SetFieldByName(AuctionAssignmentNote input, string fieldName, string value)
|
||||
{
|
||||
try
|
||||
{
|
||||
switch (fieldName)
|
||||
{
|
||||
case "Cn. Acc. #":
|
||||
input.AuctionCustomerNo = value;
|
||||
break;
|
||||
case "Origin":
|
||||
input.Origin = value;
|
||||
break;
|
||||
case "Writer":
|
||||
input.Writer = value;
|
||||
break;
|
||||
case "Type":
|
||||
input.Type = value;
|
||||
break;
|
||||
case "Text":
|
||||
input.Text = value;
|
||||
break;
|
||||
case "Date Written":
|
||||
input.DateWritten = !string.IsNullOrWhiteSpace(value)
|
||||
? DateTime.Parse(value.Substring(0, value.Trim().Length - 4))
|
||||
: (DateTime?) null;
|
||||
break;
|
||||
|
||||
default:
|
||||
Log.Error("Got unknown field {fieldName} with value {value} for AuctionAssignmentNote");
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failed to set field {fieldName} with value {value} for AuctionAssignmentNote.");
|
||||
}
|
||||
|
||||
return input;
|
||||
}
|
||||
}
|
||||
}
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using LCU.API.Collections.RepoApi.Configuration;
|
||||
using LCU.API.Collections.RepoApi.Services.AutoIms;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public class AutoImsService
|
||||
{
|
||||
private readonly IImsAutoFileDataConverter _imsAutoFileDataConverter;
|
||||
private readonly IImsAutoFileNoteConverter _imsAutoFileNoteConverter;
|
||||
private readonly IEmailer _emailer;
|
||||
private readonly IAutoImsDownloaderService _downloaderService;
|
||||
private readonly RepoApiConfiguration _config;
|
||||
private readonly IAutoImsImporterService _autoImsImporterService;
|
||||
|
||||
public AutoImsService(
|
||||
IAutoImsDownloaderService downloaderService,
|
||||
RepoApiConfiguration config,
|
||||
IAutoImsImporterService autoImsImporterService,
|
||||
IImsAutoFileDataConverter imsAutoFileDataConverter,
|
||||
IImsAutoFileNoteConverter imsAutoFileNoteConverter, IEmailer emailer)
|
||||
{
|
||||
_downloaderService = downloaderService;
|
||||
_config = config;
|
||||
_emailer = emailer;
|
||||
_autoImsImporterService = autoImsImporterService;
|
||||
_imsAutoFileDataConverter = imsAutoFileDataConverter;
|
||||
_imsAutoFileNoteConverter = imsAutoFileNoteConverter;
|
||||
}
|
||||
|
||||
public IEnumerable<FileInfo> Download()
|
||||
{
|
||||
Log.Information("Starting AutoIMS Download Process.");
|
||||
var files = new Dictionary<FileInfo, string>();
|
||||
|
||||
try
|
||||
{
|
||||
if (_config.DownloadNewFiles)
|
||||
{
|
||||
Log.Debug("Downloading auction files.");
|
||||
files = _downloaderService.DownLoadAuctionFiles();
|
||||
}
|
||||
|
||||
if (_config.CheckForLocalFiles)
|
||||
{
|
||||
Log.Debug("Checking for local files.");
|
||||
files = _downloaderService.CombineWithLocalFiles(files);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex,"AutoIms File Downloader Failure");
|
||||
}
|
||||
|
||||
Log.Information("Finished AutoIMS Download Process.");
|
||||
return _downloaderService.ConvertToFileList(files);
|
||||
}
|
||||
|
||||
public void ProcessDownloadedFiles(IEnumerable<FileInfo> ascendingOrderOfFiles)
|
||||
{
|
||||
Log.Debug("Starting AutoIMS Entry Processing.");
|
||||
|
||||
if (ascendingOrderOfFiles == null)
|
||||
{
|
||||
throw new Exception("Must pass a valid IEnumerable of FileInfo to ProcessDownloads.");
|
||||
}
|
||||
|
||||
foreach (var file in ascendingOrderOfFiles)
|
||||
{
|
||||
ProcessSingleFile(file);
|
||||
}
|
||||
}
|
||||
|
||||
public void ProcessSingleFile(FileInfo file)
|
||||
{
|
||||
Log.Debug("Processing Single Auto IMS File. {0}", file.FullName);
|
||||
string fileName = file.Name;
|
||||
FileStream stream = null;
|
||||
try
|
||||
{
|
||||
stream = File.OpenRead(Path.Combine(_config.LocalDirectoryPath, fileName));
|
||||
}
|
||||
catch (IOException ioException)
|
||||
{
|
||||
Log.Error(ioException, "Failed to open AutoIMS file {0}, got exception.", file.FullName);
|
||||
return;
|
||||
}
|
||||
|
||||
if (file.Name.ToLower().Contains("notes"))
|
||||
{
|
||||
ProcessSingleAutoImsNotesFile(file, stream);
|
||||
}
|
||||
else
|
||||
{
|
||||
ProcessSingleAutoImsFile(file, stream);
|
||||
}
|
||||
|
||||
if (_config.MoveProcessedFiles)
|
||||
{
|
||||
Log.Debug("Moving Files for AutoIMS Process.");
|
||||
|
||||
// create current month folder
|
||||
if (!Directory.Exists(
|
||||
Path.Combine(_config.LocalDirectoryPath, DateTime.Now.ToString("MMM yyyy"))))
|
||||
{
|
||||
var newDir = Path.Combine(_config.LocalDirectoryPath, DateTime.Now.ToString("MMM yyyy"));
|
||||
Log.Information("Creating folder {0}", newDir);
|
||||
Directory.CreateDirectory(newDir);
|
||||
}
|
||||
|
||||
var sourceFileName = Path.Combine(_config.LocalDirectoryPath, fileName);
|
||||
var destFileName =
|
||||
Path.Combine(_config.LocalDirectoryPath, DateTime.Now.ToString("MMM yyyy"), fileName);
|
||||
Log.Information("Moving File {0} to Folder {1}.", sourceFileName, destFileName);
|
||||
File.Move(sourceFileName, destFileName);
|
||||
}
|
||||
}
|
||||
|
||||
public void ProcessSingleAutoImsNotesFile(FileInfo file, FileStream stream)
|
||||
{
|
||||
Log.Debug("Processing notes file {0}.", file);
|
||||
|
||||
try
|
||||
{
|
||||
IEnumerable<AuctionAssignmentNote> data =
|
||||
_imsAutoFileNoteConverter.CsvToImsAutoFileNotes(stream);
|
||||
data = _imsAutoFileNoteConverter.CompressIdentifyingRows(data);
|
||||
_autoImsImporterService.ImportNotesToDatabase(data);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex,"AutoIMS Note Import Failure");
|
||||
}
|
||||
}
|
||||
|
||||
public void ProcessSingleAutoImsFile(FileInfo file, FileStream stream)
|
||||
{
|
||||
Log.Debug("Processing data file {0}.", file);
|
||||
|
||||
try
|
||||
{
|
||||
IReadOnlyCollection<ImsAutoFileData> models = _imsAutoFileDataConverter.CsvToImsAutoFileData(stream);
|
||||
var imported = _autoImsImporterService.ImportDataFile(models);
|
||||
var floors = _autoImsImporterService.SetAllAvailableFloors();
|
||||
_emailer.SendRefundProcessStartEmails(imported);
|
||||
_emailer.SendSummaryEmail(imported, floors);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex,"AutoIMS Data Import Failure");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+123
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlClient;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Exceptions;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Repositories;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public class CollateralService : ICollateralService
|
||||
{
|
||||
private readonly ICollateralRepository _collateralRepository;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
||||
public CollateralService(
|
||||
ICollateralRepository collateralRepository,
|
||||
IUnitOfWork unitOfWork)
|
||||
{
|
||||
_collateralRepository = collateralRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<Collateral>> ListAll()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _collateralRepository.ListAllAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving Collaterals");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Collateral> SaveAsync(Collateral collateral)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _collateralRepository.AddAsync(collateral);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
|
||||
return collateral;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure saving Collateral: {collateral}", collateral);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Collateral> Get(int id)
|
||||
{
|
||||
Collateral collateral;
|
||||
try
|
||||
{
|
||||
collateral = await _collateralRepository.FindByIdAsync(id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving Collateral with id: {id}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
|
||||
if (collateral == null)
|
||||
{
|
||||
throw new NotFoundException($"Collateral with id {id} not found.");
|
||||
}
|
||||
|
||||
return collateral;
|
||||
}
|
||||
|
||||
public async Task<Collateral> UpdateAsync(Collateral collateral)
|
||||
{
|
||||
Collateral existing = null;
|
||||
|
||||
try
|
||||
{
|
||||
existing = await _collateralRepository.FindByIdAsync(collateral.CollateralId);
|
||||
}catch(Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving existing collateral with collateralId: {collateral.CollateralId}",collateral);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
|
||||
|
||||
if (existing == null)
|
||||
{
|
||||
throw new NotFoundException($"Collateral with id {collateral.CollateralId} not found.");
|
||||
}
|
||||
|
||||
existing.Vin = collateral.Vin;
|
||||
existing.Make = collateral.Make;
|
||||
existing.Model = collateral.Model;
|
||||
existing.Year = collateral.Year;
|
||||
existing.Description = collateral.Description;
|
||||
existing.VehicleType = collateral.VehicleType;
|
||||
existing.BodyStyle = collateral.BodyStyle;
|
||||
existing.Inoperable = collateral.Inoperable;
|
||||
existing.CollateralTypeId = collateral.CollateralTypeId;
|
||||
existing.RetitledInLandmarkName = collateral.RetitledInLandmarkName;
|
||||
|
||||
try
|
||||
{
|
||||
_collateralRepository.Update(existing);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return (existing);
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure updating Collateral: {collateral}", collateral);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure updating Collateral: {collateral}", collateral);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,203 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Configuration;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.AutoIms;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Consumer;
|
||||
using LCU.Shared.Common.NotificationApiLib.Models;
|
||||
using Microsoft.EntityFrameworkCore.Internal;
|
||||
using Newtonsoft.Json;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public class Emailer : IEmailer
|
||||
{
|
||||
private readonly IHttpClientFactory _httpClientFactory;
|
||||
private readonly RepoApiConfiguration _configuration;
|
||||
private readonly IEventAlertService _eventAlertService;
|
||||
private const string O4RStatus = "O4R";
|
||||
|
||||
public Emailer(IHttpClientFactory httpClientFactory, RepoApiConfiguration configuration, IEventAlertService eventAlertService)
|
||||
{
|
||||
_httpClientFactory = httpClientFactory;
|
||||
_configuration = configuration;
|
||||
_eventAlertService = eventAlertService;
|
||||
}
|
||||
|
||||
public async Task<HttpResponseMessage> SendMessageAsync(List<string> recipient, string subject, string body)
|
||||
{
|
||||
var emailRequest = new EmailRequest
|
||||
{
|
||||
Recipient = recipient,
|
||||
Subject = subject,
|
||||
Body = body
|
||||
};
|
||||
var encoded = JsonConvert.SerializeObject(emailRequest);
|
||||
|
||||
try
|
||||
{
|
||||
var client = _httpClientFactory.CreateClient();
|
||||
var c = new StringContent(encoded, Encoding.UTF8, "application/json");
|
||||
HttpResponseMessage response = await client.PostAsync(_configuration.NotificationsEmailUrl, c);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
return response;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "An exception was caught trying to send an email via Notifications API.");
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<HttpResponseMessage> SendFloorPriceAcceptedEmail(bool isManual, string accountNo, decimal? oldFloorPrice, decimal? newFloorPrice)
|
||||
{
|
||||
var subject = isManual ? "Manual Floor Price Set" : "Automated Floor Price Set";
|
||||
var body = string.Format(subject + " for account {0} from {1} to {2}.", accountNo, oldFloorPrice?.ToString("C2", CultureInfo.CurrentCulture), newFloorPrice?.ToString("C2", CultureInfo.CurrentCulture));
|
||||
|
||||
return await SendMessageAsync(_configuration.FloorPriceAcceptedRecipients, subject, body);
|
||||
}
|
||||
|
||||
public async Task<HttpResponseMessage> SendSurplusDeficiencyLetterEmailAsync(RepoAccount repoAccount)
|
||||
{
|
||||
var message = "Please Send the Surplus Deficiency letter. Please notify the Repo Specialists when the letter has been sent, so that this account can be closed in Repo Manager.<br/><br/>";
|
||||
var member = repoAccount.Borrowers?.First(b => b.OwnershipType == 6);
|
||||
var memberName = "Member Name: " + member?.IndividualName + "<br/>";
|
||||
var accountNo = "Account No: " + repoAccount.AccountNo + "<br/>";
|
||||
|
||||
return await SendMessageAsync(_configuration.CollectionsClerkRecipients, "Refunds Recieved", string.Concat(message, memberName, accountNo));
|
||||
}
|
||||
|
||||
public async Task<HttpResponseMessage> MaybeSendO4rNoCollateralEmail(RepoAccount repoAccount)
|
||||
{
|
||||
if (repoAccount != null && repoAccount.DateClosed == null && repoAccount.Status == O4RStatus)
|
||||
{
|
||||
var subject = "O4R No Collateral";
|
||||
var body = string.Format("Account {0} set to O4R with no collateral.", repoAccount.AccountNo);
|
||||
|
||||
return await SendMessageAsync(_configuration.RepoManagerAdminRecipients, subject, body);
|
||||
}
|
||||
|
||||
return new HttpResponseMessage { StatusCode = HttpStatusCode.OK };
|
||||
}
|
||||
|
||||
public async Task<HttpResponseMessage> SendAlertSummaryEmailWithEndTime(DateTime endTime)
|
||||
{
|
||||
var startTime = endTime.AddHours(-1);
|
||||
var eventAlerts = _eventAlertService.ListWithinDateRange(startTime, endTime).GetAwaiter().GetResult();
|
||||
var subject = "Repo Manager Alerts " + endTime.ToString("MM-dd-yyyy HH:mm:ss");
|
||||
var body = "<h2>" + subject + "</h2><br/>";
|
||||
|
||||
foreach (var eventAlert in eventAlerts)
|
||||
{
|
||||
var entry = CreateAlertEmailEntry(eventAlert);
|
||||
if (!string.IsNullOrEmpty(entry))
|
||||
{
|
||||
body += CreateAlertEmailEntry(eventAlert) + "<br/><br/>";
|
||||
}
|
||||
}
|
||||
|
||||
if (!eventAlerts.Any())
|
||||
{
|
||||
body += "No new alerts.";
|
||||
}
|
||||
|
||||
return await SendMessageAsync(_configuration.CollectionsRecipients, subject, body);
|
||||
}
|
||||
|
||||
private string CreateAlertEmailEntry(EventAlert eventAlert)
|
||||
{
|
||||
var accountNo = "Account No: " + eventAlert.AccountNo + "<br/>";
|
||||
var alert = "Alert: " + eventAlert.Message + "<br/>";
|
||||
var url = eventAlert.RepoId != null
|
||||
? _configuration.RepoManagerAccountUrl + eventAlert.RepoId
|
||||
: "";
|
||||
var link = eventAlert.RepoId != null
|
||||
? string.Format("<a href={0}>{0}</a>", url) + "<br/>"
|
||||
: "";
|
||||
|
||||
return string.Concat(accountNo, alert, link);
|
||||
}
|
||||
|
||||
public void SendSummaryEmail(IReadOnlyCollection<ImportResultRow> imported, IReadOnlyCollection<ImportResultRow> floors)
|
||||
{
|
||||
Log.Debug("Sending AutoIMS Import Email");
|
||||
var subject = "Landmark Auto IMS Import " + DateTime.Now;
|
||||
var reports = new List<string>();
|
||||
|
||||
if (imported == null || floors == null)
|
||||
{
|
||||
Log.Error("Null result sets passed to SendSummaryEmail");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var importResultRow in imported)
|
||||
{
|
||||
reports.Add(importResultRow.BuildStatusReport(_configuration.RepoManagerAccountUrl));
|
||||
reports.Add("=====================================================================================================<br/>");
|
||||
}
|
||||
|
||||
foreach (var importResultRow in floors)
|
||||
{
|
||||
reports.Add(importResultRow.BuildStatusReport(_configuration.RepoManagerAccountUrl));
|
||||
}
|
||||
|
||||
var result = reports.Join("<br/>");
|
||||
SendMessageAsync(_configuration.CreditDebitsRecipients, subject, result).GetAwaiter().GetResult();
|
||||
Log.Debug("AutoIMS Import Email Sent");
|
||||
}
|
||||
|
||||
public void SendRefundProcessStartEmails(IReadOnlyCollection<ImportResultRow> models)
|
||||
{
|
||||
if (models == null || !models.Any())
|
||||
{
|
||||
Log.Warning("No models to process sold emails for.");
|
||||
return;
|
||||
}
|
||||
var soldResults = models.Where(x => x.Data?.SoldDate?.Date == DateTime.Now.Date).ToList();
|
||||
|
||||
foreach (var sale in soldResults)
|
||||
{
|
||||
SendMessageAsync(_configuration.CollectionsClerkRecipients, "Auction sold, please start the refund process", CreateRefundProcessEmailBody(sale)).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
|
||||
public string CreateRefundProcessEmailBody(ImportResultRow sale)
|
||||
{
|
||||
if (sale?.RepoAccount == null || sale.Data == null || sale.RepoAccount.Borrowers == null)
|
||||
{
|
||||
Log.Error("Invalid data, can't create email body for account.");
|
||||
return "";
|
||||
}
|
||||
var member = sale.RepoAccount.Borrowers?.First(b => b.OwnershipType == 6);
|
||||
var individualName = member != null ? member.IndividualName : "No Primary Owner!";
|
||||
|
||||
var memberName = "Member Name: " + individualName + "<br/>";
|
||||
var accountNo = "Account No: " + sale.RepoAccount.AccountNo + "<br/>";
|
||||
var collateralVin = "VIN: " + sale.Data.VIN + "<br/>";
|
||||
|
||||
return string.Concat(memberName, accountNo, collateralVin);
|
||||
}
|
||||
|
||||
public async Task<HttpResponseMessage> SendProccessSummary(RepoRunInfo runInfo)
|
||||
{
|
||||
|
||||
string subject = $"Repo Processor Run Receipt [{runInfo.Receipts.Count}]";
|
||||
var receiptList = runInfo.Receipts.Select(receipt => receipt.Pretty());
|
||||
string bodyData = string.Join("<br/>", receiptList);
|
||||
string body = $"Process Started: {runInfo.StartDateTime} and completed at {runInfo.CompleteDateTime}<br/><br/>";
|
||||
|
||||
return await SendMessageAsync(_configuration.RepoManagerAdminRecipients, subject, body + bodyData);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
+297
@@ -0,0 +1,297 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Exceptions;
|
||||
using LCU.API.Collections.RepoApi.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Exceptions;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Repositories;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public class EventAlertService : IEventAlertService
|
||||
{
|
||||
private readonly IEventAlertRepository _eventAlertRepository;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private const string RedeemedStatus = "REDEEMED";
|
||||
private const string CancelledStatus = "RECA";
|
||||
private readonly IRepoAccountRepository _accountRepository;
|
||||
|
||||
public EventAlertService(
|
||||
IEventAlertRepository eventAlertRepository,
|
||||
IUnitOfWork unitOfWork,
|
||||
IRepoAccountRepository accountRepository)
|
||||
{
|
||||
_eventAlertRepository = eventAlertRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
_accountRepository = accountRepository;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<EventAlert>> ListAll()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _eventAlertRepository.ListAllAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving All Event Alerts");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<EventAlert>> ListUnread()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _eventAlertRepository.ListUnreadAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving Unread Event Alerts");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<EventAlert>> ListWithinDateRange(DateTime start, DateTime end)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _eventAlertRepository.ListWithinDateRangeAsync(start, end);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving Event Alerts between {start} and {end}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<EventAlert>> ListActionNeeded()
|
||||
{
|
||||
try
|
||||
{
|
||||
var repoNoAuction = await _eventAlertRepository.ListRepoNoAuctionAsync();
|
||||
var repoNoCompany = await _eventAlertRepository.ListRepoNoCompanyAsync();
|
||||
var o4rNoCompany = await _eventAlertRepository.ListO4rNoCompanyAsync();
|
||||
var retitle = await _eventAlertRepository.ListReadyForRetitleAsync();
|
||||
var floorUpdateFail = await _eventAlertRepository.ListFloorUpdateFailedAsync();
|
||||
var setFloor = await _eventAlertRepository.ListFloorNeedsSettingAsync();
|
||||
|
||||
return repoNoAuction.Concat(repoNoCompany).Concat(o4rNoCompany).Concat(retitle).Concat(floorUpdateFail).Concat(setFloor).ToList();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving Action Needed Event Alerts");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<EventAlert> SaveAsync(EventAlert eventAlert)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (IsStatusCancelledOrRedeemed(eventAlert.RepoId.Value).Result)
|
||||
{
|
||||
throw new UnwantedStatusException(eventAlert.RepoId.Value, eventAlert.AccountNo);
|
||||
}
|
||||
await _eventAlertRepository.AddAsync(eventAlert);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return eventAlert;
|
||||
}
|
||||
catch (DuplicateResourceException ex)
|
||||
{
|
||||
Log.Debug(ex, $"SaveAsync: Valid Alert Already Exists eventAlert:{eventAlert}", eventAlert);
|
||||
throw ex;
|
||||
}
|
||||
catch(UnwantedStatusException ex)
|
||||
{
|
||||
Log.Error(ex, $"SaveAsync: Status of Repo is Redeemed or Cancelled. Event Alert: {eventAlert} not created", eventAlert);
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"SaveAsync: Failure saving Event Alert: {eventAlert}", eventAlert);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<EventAlert>> SaveRangeAsync(List<EventAlert> eventAlerts)
|
||||
{
|
||||
IEnumerable<EventAlert> addedEventAlerts = new List<EventAlert>();
|
||||
try
|
||||
{
|
||||
addedEventAlerts = await _eventAlertRepository.AddRangeAsync(eventAlerts);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"EventAlertService: Failure Saving Range Of Event Alerts: {eventAlerts}", eventAlerts);
|
||||
}
|
||||
|
||||
return addedEventAlerts;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public async Task<EventAlert> Get(int id)
|
||||
{
|
||||
EventAlert eventAlert;
|
||||
try
|
||||
{
|
||||
eventAlert = await _eventAlertRepository.FindByIdAsync(id);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving Event Alert with id: {id}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
|
||||
if (eventAlert == null)
|
||||
{
|
||||
throw new NotFoundException($"EventAlert with id {id} not found.");
|
||||
}
|
||||
|
||||
return eventAlert;
|
||||
}
|
||||
|
||||
public async Task<EventAlert> UpdateAsync(EventAlert eventAlert, AppUser user)
|
||||
{
|
||||
|
||||
EventAlert existingEventAlert = null;
|
||||
try
|
||||
{
|
||||
existingEventAlert = await _eventAlertRepository.FindByIdAsync(eventAlert.Id);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failed to find existing event alert with Id: {eventAlert.Id}, user: {user}", eventAlert, user);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
|
||||
if (existingEventAlert == null)
|
||||
{
|
||||
throw new NotFoundException($"EventAlert with id {eventAlert.Id} not found.");
|
||||
}
|
||||
|
||||
existingEventAlert.Message = eventAlert.Message;
|
||||
existingEventAlert.SubMessage = eventAlert.SubMessage;
|
||||
if (existingEventAlert.DateMarkedRead == null && eventAlert.DateMarkedRead != null)
|
||||
{
|
||||
existingEventAlert.MarkedReadBy = user.FirstName + " " + user.LastName;
|
||||
existingEventAlert.DateMarkedRead = eventAlert.DateMarkedRead;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_eventAlertRepository.Update(existingEventAlert);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return (existingEventAlert);
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure updating Event Alert: {eventAlert}", eventAlert);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure updating Event Alert {eventAlert}", eventAlert);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<EventAlert> DeleteAsync(int id)
|
||||
{
|
||||
EventAlert existingEventAlert = null;
|
||||
try
|
||||
{
|
||||
existingEventAlert = await _eventAlertRepository.FindByIdAsync(id);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failed to find existing event alert with Id: {id}.");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
if (existingEventAlert == null)
|
||||
{
|
||||
throw new NotFoundException($"EventAlert with id {id} not found.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_eventAlertRepository.Remove(existingEventAlert);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return existingEventAlert;
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure deleting Event Alert with id {id}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure deleting Event Alert with id {id}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<EventAlert>> MarkAllAsRead(string accountNo)
|
||||
{
|
||||
IEnumerable<EventAlert> existingEventAlerts = null;
|
||||
try
|
||||
{
|
||||
existingEventAlerts = await _eventAlertRepository.ListUnreadByAccountNoAsync(accountNo);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failed to find existing event alerts with accountNo: {accountNo}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
|
||||
if (existingEventAlerts == null)
|
||||
{
|
||||
throw new NotFoundException($"EventAlerts for accountNo: {accountNo} not found.");
|
||||
}
|
||||
|
||||
var ret = new List<EventAlert>();
|
||||
|
||||
foreach (EventAlert eventAlert in existingEventAlerts)
|
||||
{
|
||||
eventAlert.MarkedReadBy = "System";
|
||||
eventAlert.DateMarkedRead = DateTime.Now;
|
||||
|
||||
try
|
||||
{
|
||||
_eventAlertRepository.Update(eventAlert);
|
||||
ret.Add(eventAlert);
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure updating Event Alert: {eventAlert} in the list {existingEventAlerts}. Will not commit any changes.", eventAlert);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure updating Event Alert {eventAlert} in the list {existingEventAlerts}. Will not commit any changes", eventAlert);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return ret;
|
||||
}
|
||||
|
||||
private async Task<bool> IsStatusCancelledOrRedeemed(int repoId)
|
||||
{
|
||||
var repo = await _accountRepository.FindByIdAsync(repoId);
|
||||
if (repo == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return repo.Status.Equals(CancelledStatus) || repo.Status.Equals(RedeemedStatus);
|
||||
}
|
||||
}
|
||||
}
|
||||
+253
@@ -0,0 +1,253 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Models.Responses;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Repositories;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public class FactoidService : IFactoidService
|
||||
{
|
||||
private readonly IReportingRepository _reportingRepository;
|
||||
private readonly IRepoAccountRepository _repoAccountRepository;
|
||||
private readonly IImsAutoFileDataRepository _imsAutoFileDataRepository;
|
||||
private const string O4RStatus = "O4R";
|
||||
private const string RepoStatus = "REPO";
|
||||
private const string RedeemedStatus = "REDEEMED";
|
||||
|
||||
public FactoidService(
|
||||
IReportingRepository reportingRepository,
|
||||
IRepoAccountRepository repoAccountRepository,
|
||||
IImsAutoFileDataRepository imsAutoFileDataRepository)
|
||||
{
|
||||
_reportingRepository = reportingRepository;
|
||||
_repoAccountRepository = repoAccountRepository;
|
||||
_imsAutoFileDataRepository = imsAutoFileDataRepository;
|
||||
}
|
||||
|
||||
public int FindAuctionIndex(IReadOnlyCollection<string> auctionHouses, string auctionId)
|
||||
{
|
||||
if (auctionId != null)
|
||||
{
|
||||
foreach (var item in auctionHouses.Select((value, index) => new {index, value}))
|
||||
{
|
||||
if (item.value == auctionId.Trim().ToLower())
|
||||
{
|
||||
return item.index;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return auctionHouses.Count - 1;
|
||||
}
|
||||
|
||||
|
||||
public async Task<FactoidResponse> GetCurrentInventory()
|
||||
{
|
||||
var accountsInRepo = await _repoAccountRepository.FindByCurrentStatus(RepoStatus);
|
||||
|
||||
var numCollateral = accountsInRepo.Aggregate(0, (total, curAccount) =>
|
||||
total += curAccount.Collaterals.Count());
|
||||
|
||||
return new FactoidResponse
|
||||
{
|
||||
CumulativeValue = numCollateral.ToString(),
|
||||
Name = "Vehicles",
|
||||
Description = "Current Inventory (REPO)",
|
||||
FontAwesomeIcon = "fa-car",
|
||||
Records = accountsInRepo
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<FactoidResponse> GetAllAccountsO4R()
|
||||
{
|
||||
var filteredRepoAccounts = await _reportingRepository.GetAllAccountsInO4R();
|
||||
return new FactoidResponse
|
||||
{
|
||||
CumulativeValue = filteredRepoAccounts.Count().ToString(),
|
||||
Name = "Accounts",
|
||||
Description = "Total Accounts in O4R",
|
||||
FontAwesomeIcon = "fa-car",
|
||||
Records = filteredRepoAccounts
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<FactoidResponse> GetPercentFloorPrice(DateTime lowDateTime,
|
||||
DateTime highDateTime)
|
||||
{
|
||||
var soldImsAccounts = await _imsAutoFileDataRepository.GetImsSoldRecordsForPeriod(lowDateTime, highDateTime);
|
||||
|
||||
decimal? percentFloor = 0m;
|
||||
if (soldImsAccounts.Count() != 0)
|
||||
{
|
||||
percentFloor = soldImsAccounts.Select(x => x.GrossPercentFloor).Sum() / soldImsAccounts.Count;
|
||||
}
|
||||
|
||||
var records = await _repoAccountRepository.FindByRepoIdAsync(soldImsAccounts.Select(x => x.RepoId.ToString()).ToList());
|
||||
return new FactoidResponse
|
||||
{
|
||||
CumulativeValue = percentFloor?.ToString("#.#") + "%",
|
||||
Name = "Percentage of Floor",
|
||||
Description = "Last 30 Days",
|
||||
FontAwesomeIcon = "fa-money",
|
||||
Records = records
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<FactoidResponse> GetRecentlyRedeemed(DateTime lowDate, DateTime highDate)
|
||||
{
|
||||
var filteredRepoAccounts = await _repoAccountRepository.FindByStatusChangeInPeriod(RedeemedStatus, lowDate, highDate);
|
||||
return new FactoidResponse
|
||||
{
|
||||
CumulativeValue = filteredRepoAccounts.Count().ToString(),
|
||||
Name = "Recently Redeemed",
|
||||
Description = "Last 30 Days",
|
||||
FontAwesomeIcon = "fa-calendar",
|
||||
Records = filteredRepoAccounts
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<FactoidResponse> GetAvgDaysO4RToREPO(DateTime lowDate, DateTime highDate)
|
||||
{
|
||||
var recentAccountsInREPO = await _repoAccountRepository.FindByStatusChangeInPeriod(RepoStatus, lowDate, highDate);
|
||||
var avgDaysO4RtoRepo = GetAverageDaysBetweenStatuses(recentAccountsInREPO, RepoStatus, O4RStatus);
|
||||
return new FactoidResponse
|
||||
{
|
||||
CumulativeValue = avgDaysO4RtoRepo != null ? avgDaysO4RtoRepo.ToString() : "N/A",
|
||||
Name = "Avg. Days O4R to Repo",
|
||||
Description = "Last 30 Days",
|
||||
FontAwesomeIcon = "fa-calendar",
|
||||
Records = recentAccountsInREPO
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<FactoidResponse> GetGetAvgDaysDelqToO4R(DateTime lowDate, DateTime highDate)
|
||||
{
|
||||
IEnumerable<RepoAccount> recentAccountsInO4R = await _repoAccountRepository.FindByStatusChangeInPeriod(O4RStatus, lowDate, highDate);
|
||||
|
||||
var accountsWithoutDateDelinquent = recentAccountsInO4R.Where(account => account.DaysDelinquent == null);
|
||||
if (accountsWithoutDateDelinquent.Count() > 0)
|
||||
{
|
||||
Log.Error($"Accounts found without DaysDelinquent when added: {accountsWithoutDateDelinquent} ", accountsWithoutDateDelinquent);
|
||||
recentAccountsInO4R = recentAccountsInO4R.Except(accountsWithoutDateDelinquent);
|
||||
}
|
||||
|
||||
var totalAccounts = recentAccountsInO4R.Count();
|
||||
var avgDaysDelqToO4R = 0;
|
||||
if (totalAccounts > 0)
|
||||
{
|
||||
var delqToO4RSum = recentAccountsInO4R.Aggregate(0, (sum, repoAccount) => sum += repoAccount.DaysDelinquent ?? 0);
|
||||
avgDaysDelqToO4R = delqToO4RSum / totalAccounts;
|
||||
}
|
||||
|
||||
return new FactoidResponse()
|
||||
{
|
||||
CumulativeValue = avgDaysDelqToO4R.ToString(),
|
||||
Name = "Avg. Days Dlqt To O4R",
|
||||
Description = "Last 30 Days",
|
||||
FontAwesomeIcon = "fa-calendar",
|
||||
Records = recentAccountsInO4R.ToList()
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<FactoidResponse> GetGetAvgDaysRepoToSold(DateTime lowDate, DateTime highDate)
|
||||
{
|
||||
var recentAccountsSold = await _repoAccountRepository.FindByStatusChangeInPeriod("SOLD", lowDate, highDate);
|
||||
var avgDaysRepoToSold = GetAverageDaysBetweenStatuses(recentAccountsSold, "SOLD", RepoStatus);
|
||||
return new FactoidResponse
|
||||
{
|
||||
CumulativeValue = avgDaysRepoToSold != null ? avgDaysRepoToSold.ToString() : "N/A",
|
||||
Name = "Avg. Days Repo to Sold",
|
||||
Description = "Last 30 Days",
|
||||
FontAwesomeIcon = "fa-calendar",
|
||||
Records = recentAccountsSold
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<FactoidResponse> GetCurrentlyOnHold()
|
||||
{
|
||||
var accountsOnHold = await _repoAccountRepository.GetCurrentAccountsOnHoldAtAuction();
|
||||
|
||||
return new FactoidResponse
|
||||
{
|
||||
CumulativeValue = accountsOnHold.Count().ToString(),
|
||||
Name = "On Hold at Auction",
|
||||
Description = "Accounts",
|
||||
FontAwesomeIcon = "fa-car",
|
||||
Records = accountsOnHold
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<FactoidResponse> GetRecentlyRepoed(DateTime lowDate, DateTime highDate)
|
||||
{
|
||||
var accountsRecentlyRepoed = await _repoAccountRepository.FindByStatusChangeInPeriod(RepoStatus, lowDate, highDate);
|
||||
|
||||
return new FactoidResponse
|
||||
{
|
||||
CumulativeValue = accountsRecentlyRepoed.Count().ToString(),
|
||||
Name = "Recently Repoed",
|
||||
Description = "Last 30 Days",
|
||||
FontAwesomeIcon = "fa-car",
|
||||
Records = accountsRecentlyRepoed
|
||||
};
|
||||
}
|
||||
|
||||
public async Task<FactoidResponse> GetRecentlySold(DateTime lowDate, DateTime highDate)
|
||||
{
|
||||
var accountsRecentlySold = await _repoAccountRepository.FindByStatusChangeInPeriod("SOLD", lowDate, highDate);
|
||||
|
||||
return new FactoidResponse
|
||||
{
|
||||
CumulativeValue = accountsRecentlySold.Count().ToString(),
|
||||
Name = "Recently Sold",
|
||||
Description = "Last 30 Days",
|
||||
FontAwesomeIcon = "fa-car",
|
||||
Records = accountsRecentlySold
|
||||
};
|
||||
}
|
||||
|
||||
// Get the average day difference between two status changes.
|
||||
// If the accounts given don't have valid to and from status, those accounts are ignored from the calulcation.
|
||||
// However, if calling this array, the accounts SHOULD have valid status changes.
|
||||
private int? GetAverageDaysBetweenStatuses(IEnumerable<RepoAccount> repoAccount, string statusTo, string statusFrom)
|
||||
{
|
||||
IEnumerable<int?> daysBetweenStatuses = repoAccount.Select(
|
||||
(account) => GetDaysBetweenStatuses(account, statusTo, statusFrom));
|
||||
|
||||
var validRepos = daysBetweenStatuses.Aggregate(0, (sum, days) => sum += days != null ? 1 : 0);
|
||||
var sumOfDays = daysBetweenStatuses.Aggregate(0, (sum, days) => sum += days != null ? days.Value : 0);
|
||||
|
||||
if (validRepos == 0)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return sumOfDays / validRepos;
|
||||
}
|
||||
|
||||
// If either status cannot be found, return null, indicating an that days could not be calculated.
|
||||
// If both the from status and to status are found, floor their difference of dates.
|
||||
private int? GetDaysBetweenStatuses(RepoAccount repoAccount, string newStatus, string oldStatus)
|
||||
{
|
||||
var fromStatusDate = GetLatestStatusChange(repoAccount, oldStatus)?.ChangedDate;
|
||||
var toStatusDate = GetLatestStatusChange(repoAccount, newStatus)?.ChangedDate;
|
||||
|
||||
if (toStatusDate != null && fromStatusDate != null)
|
||||
{
|
||||
return (int) Math.Floor(toStatusDate.Value.Subtract(fromStatusDate.Value).TotalDays);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private RepoStatusChange GetLatestStatusChange(RepoAccount repoAccount, string status)
|
||||
{
|
||||
return (
|
||||
from statusChange in repoAccount.RepoStatusChanges
|
||||
where string.Equals(statusChange.StatusTo, status, StringComparison.CurrentCultureIgnoreCase)
|
||||
select statusChange).LastOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using LCU.API.Collections.RepoApi.Domain.FloorPricing;
|
||||
using LCU.API.Collections.RepoApi.Domain.FloorPricing.Strategies;
|
||||
using LCU.API.Collections.RepoApi.Exceptions;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.WebServices.NADA;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public class FloorPricingService : IFloorPricingService
|
||||
{
|
||||
private readonly IFloorPricingStrategyFactory _floorFloorPricingStrategyFactory;
|
||||
private readonly IImsAutoFileDataService _imsAutoFileDataService;
|
||||
private readonly IVehicleFloorPricingParametersService _vehicleFloorPricingParametersService;
|
||||
private readonly ICollateralValueService _collateralValueService;
|
||||
|
||||
|
||||
public FloorPricingService(
|
||||
IFloorPricingStrategyFactory floorFloorPricingStrategyFactory,
|
||||
IVehicleFloorPricingParametersService vehicleFloorPricingParametersService,
|
||||
IImsAutoFileDataService imsAutoFileDataService,
|
||||
ICollateralValueService collateralValueService)
|
||||
{
|
||||
_floorFloorPricingStrategyFactory = floorFloorPricingStrategyFactory;
|
||||
_vehicleFloorPricingParametersService = vehicleFloorPricingParametersService;
|
||||
_imsAutoFileDataService = imsAutoFileDataService;
|
||||
_collateralValueService = collateralValueService;
|
||||
}
|
||||
|
||||
public FloorPriceCalculationResultSet DetermineFloorPrice(int auctionAssignmentId)
|
||||
{
|
||||
CollateralValuation collateralValuation = new CollateralValuation { };
|
||||
ImsAutoFileData imsAutoFileData = new ImsAutoFileData { };
|
||||
var floorPricingParams = _vehicleFloorPricingParametersService.ListAll().GetAwaiter().GetResult();
|
||||
try
|
||||
{
|
||||
imsAutoFileData = _imsAutoFileDataService.GetByAuctionAssignmentId(auctionAssignmentId).GetAwaiter().GetResult();
|
||||
collateralValuation = GetCollateralValuation(imsAutoFileData.VIN, imsAutoFileData.StatedMileage ?? 0, 0);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Error when trying to Determine Floor Price for auctionAssignment with id: ${auctionAssignmentId}");
|
||||
}
|
||||
FloorPriceCalculationParams fpcParams = new FloorPriceCalculationParams()
|
||||
{
|
||||
VehicleFloorPricingParameters = floorPricingParams,
|
||||
ImsAutoFileData = imsAutoFileData,
|
||||
CollateralValuation = collateralValuation
|
||||
};
|
||||
|
||||
var prices = GetCalculatedFloorPrices(fpcParams);
|
||||
var price = SelectBestFloorPrice(prices);
|
||||
var floorPriceValuesUsed = PopulateFloorPriceValuesUsed(auctionAssignmentId, fpcParams, prices, price);
|
||||
FloorPriceCalculationResultSet strategicResultSet = new FloorPriceCalculationResultSet { };
|
||||
strategicResultSet.AllResults = prices;
|
||||
strategicResultSet.PrimaryResult = price;
|
||||
strategicResultSet.FloorPriceValuesUsed = floorPriceValuesUsed;
|
||||
return strategicResultSet;
|
||||
}
|
||||
|
||||
public CollateralValuation GetCollateralValuation(string VIN, int StatedMileage, int vehichleTypeKey)
|
||||
{
|
||||
CollateralValuation collateralValuation = new CollateralValuation { };
|
||||
try
|
||||
{
|
||||
collateralValuation = _collateralValueService.GetCollateralValue(VIN, StatedMileage, vehichleTypeKey);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
if (ex.Message.Contains("license"))
|
||||
{
|
||||
Log.Error(ex, "Your collateral value service license may be invalid/expired, please notify the collections department administration.");
|
||||
}
|
||||
Log.Error(ex, "NADA ERROR when getting Collateral Value");
|
||||
}
|
||||
return collateralValuation;
|
||||
}
|
||||
|
||||
public IReadOnlyCollection<FloorPriceCalculationResult> GetCalculatedFloorPrices(FloorPriceCalculationParams fpcParams)
|
||||
{
|
||||
IReadOnlyCollection<IFloorPricingStrategy> strategies =
|
||||
_floorFloorPricingStrategyFactory.GetFloorPricingStrategies();
|
||||
|
||||
List<FloorPriceCalculationResult> calculationResults = new List<FloorPriceCalculationResult>();
|
||||
|
||||
foreach (var floorPricingStrategy in strategies)
|
||||
{
|
||||
calculationResults.Add(floorPricingStrategy.CalculateFloorPrice(fpcParams));
|
||||
}
|
||||
|
||||
return calculationResults;
|
||||
}
|
||||
|
||||
public FloorPriceCalculationResult SelectBestFloorPrice(IReadOnlyCollection<FloorPriceCalculationResult> results)
|
||||
{
|
||||
var successWeight = -1;
|
||||
FloorPriceCalculationResult successResult = null;
|
||||
foreach (var result in results)
|
||||
{
|
||||
if (result.ResultWeight > successWeight && result.Success)
|
||||
{
|
||||
successWeight = result.ResultWeight;
|
||||
successResult = result;
|
||||
}
|
||||
}
|
||||
|
||||
if (successResult == null)
|
||||
{
|
||||
return new FloorPriceCalculationResult()
|
||||
{
|
||||
ResultWeight = -1,
|
||||
Success = false,
|
||||
StrategyName = "Manual",
|
||||
FloorPrice = -1
|
||||
};
|
||||
}
|
||||
|
||||
return successResult;
|
||||
}
|
||||
|
||||
public FloorPriceValuesUsed PopulateFloorPriceValuesUsed(int auctionAssignmentId, FloorPriceCalculationParams fpcParams, IReadOnlyCollection<FloorPriceCalculationResult> prices,
|
||||
FloorPriceCalculationResult bestPrice)
|
||||
{
|
||||
var result = new FloorPriceValuesUsed
|
||||
{
|
||||
AuctionAssignmentId = auctionAssignmentId,
|
||||
RepoId = fpcParams.ImsAutoFileData.RepoId,
|
||||
CollateralValuation = fpcParams.CollateralValuation
|
||||
};
|
||||
result.AuctionGuideBookPrice = fpcParams.ImsAutoFileData.AuctionGuideBookPrice;
|
||||
var autoImsResult = prices.FirstOrDefault(x => x.StrategyName.Equals(typeof(AutoImsSuggestedValue).FullName));
|
||||
result.WasAuctionGuideBookPriceUsed = autoImsResult != null && autoImsResult.Success && autoImsResult.FloorPrice == bestPrice.FloorPrice;
|
||||
var nadaMmrResult = prices.FirstOrDefault(x => x.StrategyName.Equals(typeof(NadaMmrConditionValue).FullName));
|
||||
result.MMRNationalAdjustedPrice = fpcParams.ImsAutoFileData.MMRNationalAdjustedPrice;
|
||||
result.MMRRegionalAdjustedPrice = fpcParams.ImsAutoFileData.MMRRegionalAdjustedPrice;
|
||||
result.NADALowAdjustedAuctionValue = fpcParams.CollateralValuation.LowAdjustedAuctionValue;
|
||||
result.NADAAverageAdjustedAuctionValue = fpcParams.CollateralValuation.AverageAdjustedAuctionValue;
|
||||
result.NADAHighAdjustedAuctionValue = fpcParams.CollateralValuation.HighAdjustedAuctionValue;
|
||||
result.Condition = fpcParams.ImsAutoFileData.ApprovedVehicleGrade ?? fpcParams.ImsAutoFileData.VehicleGrade;
|
||||
if (result.Condition == null || result.Condition <= 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
var vfpParams = Enumerable.First(Enumerable.Where(fpcParams.VehicleFloorPricingParameters,
|
||||
x => result.Condition >= x.LowCondition && result.Condition <= x.HighCondition));
|
||||
result.LowParam = vfpParams.LowCondition;
|
||||
result.HighParam = vfpParams.HighCondition;
|
||||
result.PercentageOf = vfpParams.PercentageOf;
|
||||
var hasMmrPrice = fpcParams.ImsAutoFileData.MMRNationalAdjustedPrice != null || fpcParams.ImsAutoFileData.MMRRegionalAdjustedPrice != null;
|
||||
result.WasAuctionGuideBookPriceUsed = autoImsResult != null && autoImsResult.Success && autoImsResult.StrategyName == bestPrice.StrategyName || (!hasMmrPrice && autoImsResult.FloorPrice > 0);
|
||||
result.WasMMRNationalAdjustedPriceUsed = nadaMmrResult != null && nadaMmrResult.Success && vfpParams.MMRAdjustedValue.ToLower().StartsWith("national") && hasMmrPrice;
|
||||
result.WasMMRRegionalAdjustedPriceUsed = nadaMmrResult != null && nadaMmrResult.Success && vfpParams.MMRAdjustedValue.ToLower().StartsWith("regional") && hasMmrPrice;
|
||||
result.WasNADALowAdjustedAuctionValueUsed = nadaMmrResult != null && nadaMmrResult.Success && vfpParams.NADAFirstValue.ToLower().StartsWith("low") || vfpParams.NADASecondValue.ToLower().StartsWith("low");
|
||||
result.WasNADAAverageAdjustedAuctionValueUsed = nadaMmrResult != null && nadaMmrResult.Success && vfpParams.NADAFirstValue.ToLower().StartsWith("avg") || vfpParams.NADASecondValue.ToLower().StartsWith("avg");
|
||||
result.WasNADAHighAdjustedAuctionValueUsed = nadaMmrResult != null && nadaMmrResult.Success && vfpParams.NADAFirstValue.ToLower().StartsWith("high") || vfpParams.NADASecondValue.ToLower().StartsWith("high");
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Models.Filters;
|
||||
using LCU.API.Collections.RepoApi.Models.Requests;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Results;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public interface IAuctionAssignmentService
|
||||
{
|
||||
Task<PagedResult<AuctionAssignment>> ListByFilterAsync(AuctionAssignmentFilter filter);
|
||||
Task<AuctionAssignment> GetByIdAsync(int id);
|
||||
Task<IReadOnlyCollection<AuctionAssignment>> GetByRepoIdAsync(int repoId);
|
||||
Task<AuctionAssignmentPostRequest> SaveAsync(AuctionAssignmentPostRequest auctionAssignmentPostRequest);
|
||||
Task<AuctionAssignment> UpdateAsync(AuctionAssignment auctionAssignment);
|
||||
Task<AuctionAssignment> RemoveAuction(int id);
|
||||
Task<IReadOnlyCollection<AuctionAssignment>> GetListByIdsAsync(IReadOnlyCollection<int> idSet);
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public interface IAuctionHouseService
|
||||
{
|
||||
Task<IEnumerable<AuctionHouse>> ListAllAsync();
|
||||
Task<AuctionHouse> GetAsync(string id);
|
||||
Task<AuctionHouse> SaveAsync(AuctionHouse auction);
|
||||
Task<AuctionHouse> UpdateAsync(AuctionHouse auction);
|
||||
Task<AuctionHouse> DeleteAsync(string id);
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Models.Filters;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Results;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public interface IAuditRecordService
|
||||
{
|
||||
Task<PagedResult<AuditRecord>> ListAll(PageFilter filter);
|
||||
Task<AuditRecord> SaveAsync(AuditRecord repoAccount);
|
||||
Task<AuditRecord> Get(int id);
|
||||
Task<AuditRecord> UpdateAsync(AuditRecord auditRecord);
|
||||
Task<AuditRecord> DeleteAsync(int id);
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public interface ICollateralService
|
||||
{
|
||||
Task<IEnumerable<Collateral>> ListAll();
|
||||
Task<Collateral> Get(int id);
|
||||
Task<Collateral> SaveAsync(Collateral Collateral);
|
||||
Task<Collateral> UpdateAsync(Collateral Collateral);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.AutoIms;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Consumer;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public interface IEmailer
|
||||
{
|
||||
Task<HttpResponseMessage> SendFloorPriceAcceptedEmail(bool isManual, string accountNo, decimal? oldFloorPrice, decimal? newFloorPrice);
|
||||
Task<HttpResponseMessage> SendSurplusDeficiencyLetterEmailAsync(RepoAccount repoAccount);
|
||||
Task<HttpResponseMessage> MaybeSendO4rNoCollateralEmail(RepoAccount repoAccount);
|
||||
Task<HttpResponseMessage> SendAlertSummaryEmailWithEndTime(DateTime endTime);
|
||||
Task<HttpResponseMessage> SendProccessSummary(RepoRunInfo runInfo);
|
||||
void SendSummaryEmail(IReadOnlyCollection<ImportResultRow> imported, IReadOnlyCollection<ImportResultRow> floors);
|
||||
void SendRefundProcessStartEmails(IReadOnlyCollection<ImportResultRow> imported);
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public interface IEventAlertService
|
||||
{
|
||||
Task<IEnumerable<EventAlert>> ListAll();
|
||||
Task<IEnumerable<EventAlert>> ListUnread();
|
||||
Task<IEnumerable<EventAlert>> ListActionNeeded();
|
||||
Task<IEnumerable<EventAlert>> ListWithinDateRange(DateTime start, DateTime end);
|
||||
Task<EventAlert> Get(int id);
|
||||
Task<EventAlert> SaveAsync(EventAlert eventAlert);
|
||||
Task<IEnumerable<EventAlert>> SaveRangeAsync(List<EventAlert> eventAlerts);
|
||||
Task<EventAlert> UpdateAsync(EventAlert eventAlert, AppUser user);
|
||||
Task<EventAlert> DeleteAsync(int id);
|
||||
Task<IEnumerable<EventAlert>> MarkAllAsRead(string eventAlert);
|
||||
}
|
||||
}
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Models.Responses;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public interface IFactoidService
|
||||
{
|
||||
Task<FactoidResponse> GetAllAccountsO4R();
|
||||
Task<FactoidResponse> GetCurrentInventory();
|
||||
int FindAuctionIndex(IReadOnlyCollection<string> auctionHouses, string auctionId);
|
||||
Task<FactoidResponse> GetPercentFloorPrice(DateTime lowDateTime, DateTime highDateTime);
|
||||
Task<FactoidResponse> GetRecentlyRedeemed(DateTime lowDate, DateTime highDate);
|
||||
Task<FactoidResponse> GetAvgDaysO4RToREPO(DateTime lowDate, DateTime highDate);
|
||||
Task<FactoidResponse> GetGetAvgDaysDelqToO4R(DateTime lowDate, DateTime highDate);
|
||||
Task<FactoidResponse> GetGetAvgDaysRepoToSold(DateTime lowDate, DateTime highDate);
|
||||
Task<FactoidResponse> GetRecentlyRepoed(DateTime lowDate, DateTime highDate);
|
||||
Task<FactoidResponse> GetRecentlySold(DateTime lowDate, DateTime highDate);
|
||||
Task<FactoidResponse> GetCurrentlyOnHold();
|
||||
}
|
||||
}
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
using System.Collections.Generic;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public interface IFloorPricingService
|
||||
{
|
||||
FloorPriceCalculationResultSet DetermineFloorPrice(int auctionAssignmentId);
|
||||
IReadOnlyCollection<FloorPriceCalculationResult> GetCalculatedFloorPrices(FloorPriceCalculationParams fpcParams);
|
||||
FloorPriceCalculationResult SelectBestFloorPrice(IReadOnlyCollection<FloorPriceCalculationResult> results);
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public interface IImsAutoFileDataService
|
||||
{
|
||||
Task<IEnumerable<ImsAutoFileData>> ListAll();
|
||||
Task<ImsAutoFileData> SaveAsync(ImsAutoFileData imsAutoFileData);
|
||||
Task<ImsAutoFileData> Get(string vin, int repoId);
|
||||
Task<ImsAutoFileData> GetByAuctionAssignmentId(int auctionAssignmentId);
|
||||
Task<ImsAutoFileData> UpdateAsync(ImsAutoFileData imsAutoFileData);
|
||||
Task<ImsAutoFileData> DeleteAsync(string vin, int repoId);
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public interface IProcessingReceiptService
|
||||
{
|
||||
Task<IReadOnlyCollection<ProcessingReceipt>> FindReceiptsAsync(string accountNo,
|
||||
string accountId = "", string caseId = "");
|
||||
|
||||
Task<IReadOnlyCollection<ProcessingReceipt>> GetReceiptsByAccountNoAsync(string accountNo);
|
||||
Task<IReadOnlyCollection<ProcessingReceipt>> GetRetryListAsync();
|
||||
Task SaveAsync(ProcessingReceipt receipt);
|
||||
Task SaveAllAsync(IReadOnlyCollection<ProcessingReceipt> receipts);
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public interface IRefundStatusService
|
||||
{
|
||||
Task<IReadOnlyCollection<RefundStatus>> ListAllAsync();
|
||||
Task<RefundStatus> GetAsync(int repoId);
|
||||
Task<RefundStatus> SaveAsync(RefundStatus refundStatus);
|
||||
Task<RefundStatus> UpdateAsync(RefundStatus refundStatus);
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Models;
|
||||
using LCU.API.Collections.RepoApi.Models.Filters;
|
||||
using LCU.API.Collections.RepoApi.Models.Requests;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Producer;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Results;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public interface IRepoAccountService
|
||||
{
|
||||
Task<PagedResult<RepoAccount>> ListAll(PageFilter pageFilter, RepoAccountPostFilter repoAccountFilter = null);
|
||||
Task<RepoAccount> FindByAuctionAssignmentId(int auctionAssignmentId);
|
||||
Task<RepoAccount> SaveAsync(RepoAccount repoAccount);
|
||||
Task<RepoAccount> Get(int id);
|
||||
Task<IReadOnlyCollection<RepoAccount>> GetByAccountNo(string accountNo);
|
||||
Task<RepoAccount> GetNewestOpenAccount(string accountNo);
|
||||
Task<IReadOnlyCollection<RepoAccount>> SearchRepoAccounts(string searchPhrase, int maxSearchResults);
|
||||
Task<RepoAccount> UpdateAsync(RepoAccount repoAccount, AppUser currentUser);
|
||||
Task<RepoAccount> DeleteAsync(int id);
|
||||
Task<IReadOnlyCollection<ProcessingItem>> GetItemsForProcessing();
|
||||
|
||||
RepoAccount SelectFirstOpenAccount(IReadOnlyCollection<RepoAccount> repoAccounts);
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public interface IRepoCompanySelectionService
|
||||
{
|
||||
Task<IEnumerable<RepoCompanySelection>> ListAllAsync();
|
||||
Task<RepoCompanySelection> GetAsync(int id);
|
||||
Task SaveChangesAsync(IList<RepoCompanySelection> repoCompanySelections);
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public interface IRepoCompanyService
|
||||
{
|
||||
Task<IEnumerable<RepoCompany>> ListAll();
|
||||
Task<RepoCompany> Save(RepoCompany repoCompany);
|
||||
Task<RepoCompany> Get(int id);
|
||||
Task<RepoCompany> Update(RepoCompany repoCompany, AppUser user);
|
||||
Task<RepoCompany> Delete(int id);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public interface IRepoLetterService
|
||||
{
|
||||
Task<IReadOnlyCollection<RepoLetter>> ListByAccountIdAsync(int accountId);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public interface IRepoStatusChangeService
|
||||
{
|
||||
Task<IEnumerable<RepoStatusChange>> ListByAccount(int repoId);
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public interface ISavedLocationService
|
||||
{
|
||||
Task<IEnumerable<SavedLocation>> ListAllAsync();
|
||||
Task<SavedLocation> SaveAsync(SavedLocation savedLocation);
|
||||
Task<SavedLocation> GetAsync(int id);
|
||||
Task<SavedLocation> UpdateAsync(SavedLocation savedLocation);
|
||||
Task<SavedLocation> DeleteAsync(int id);
|
||||
}
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Settings;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public interface ISettingsService
|
||||
{
|
||||
Task<Settings> GetCachedRepoSettingsAsync();
|
||||
Task<Settings> ListAllSettingsAsync();
|
||||
Task<AccountType> SaveAsync(AccountType accountType);
|
||||
Task<AllowedStatusChange> SaveAsync(AllowedStatusChange statusChange);
|
||||
Task<BodyStyle> SaveAsync(BodyStyle bodyStyle);
|
||||
Task<CleaningLevel> SaveAsync(CleaningLevel cleaningLevel);
|
||||
Task<CollateralType> SaveAsync(CollateralType collateralType);
|
||||
Task<CollateralTypeToVehicleType> SaveAsync(CollateralTypeToVehicleType collateralToVehicle);
|
||||
Task<HoldStatus> SaveAsync(HoldStatus holdStatus);
|
||||
Task<LoanCategory> SaveAsync(LoanCategory loanCategory);
|
||||
Task<LocationType> SaveAsync(LocationType locationType);
|
||||
Task<VehicleType> SaveAsync(VehicleType vehicleType);
|
||||
Task<AccountType> UpdateAsync(AccountType accountType);
|
||||
Task<AllowedStatusChange> UpdateAsync(AllowedStatusChange statusChange);
|
||||
Task<BodyStyle> UpdateAsync(BodyStyle bodyStyle);
|
||||
Task<CleaningLevel> UpdateAsync(CleaningLevel cleaningLevel);
|
||||
Task<CollateralType> UpdateAsync(CollateralType collateralType);
|
||||
Task<CollateralTypeToVehicleType> UpdateAsync(CollateralTypeToVehicleType collateralToVehicle);
|
||||
Task<HoldStatus> UpdateAsync(HoldStatus holdStatus);
|
||||
Task<LoanCategory> UpdateAsync(LoanCategory loanCategory);
|
||||
Task<LocationType> UpdateAsync(LocationType locationType);
|
||||
Task<VehicleType> UpdateAsync(VehicleType vehicleType);
|
||||
Task SyncExternalUsersAsync(IReadOnlyCollection<ExternalUser> users);
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public interface IVehicleFloorPricingParametersService
|
||||
{
|
||||
Task<IEnumerable<VehicleFloorPricingParameters>> ListAll();
|
||||
Task<VehicleFloorPricingParameters> Get(int id);
|
||||
Task<VehicleFloorPricingParameters> UpdateAsync(VehicleFloorPricingParameters parameters);
|
||||
}
|
||||
}
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlClient;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Exceptions;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Repositories;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public class ImsAutoFileDataService : IImsAutoFileDataService
|
||||
{
|
||||
private readonly IImsAutoFileDataRepository _imsAutoFileDataRepository;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
||||
public ImsAutoFileDataService(
|
||||
IImsAutoFileDataRepository imsAutoFileDataRepository,
|
||||
IUnitOfWork unitOfWork)
|
||||
{
|
||||
_imsAutoFileDataRepository = imsAutoFileDataRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<ImsAutoFileData>> ListAll()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _imsAutoFileDataRepository.ListAllAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving ImsAutoFileData Set");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ImsAutoFileData> SaveAsync(ImsAutoFileData imsAutoFileData)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _imsAutoFileDataRepository.AddAsync(imsAutoFileData);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
|
||||
return imsAutoFileData;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure saving IMS Auto File Data: {imsAutoFileData}", imsAutoFileData);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ImsAutoFileData> Get(string vin, int repoId)
|
||||
{
|
||||
ImsAutoFileData imsAutoFileData;
|
||||
try
|
||||
{
|
||||
imsAutoFileData = await _imsAutoFileDataRepository.FindByIdAsync(vin, repoId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving IMS Auto File Data with vin: {vin} repoId: {repoId}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
|
||||
if (imsAutoFileData == null)
|
||||
{
|
||||
throw new NotFoundException($"IMS Auto File Data id {vin} / {repoId} not found.");
|
||||
}
|
||||
|
||||
return imsAutoFileData;
|
||||
}
|
||||
|
||||
public async Task<ImsAutoFileData> GetByAuctionAssignmentId(int auctionAssignmentId)
|
||||
{
|
||||
ImsAutoFileData imsAutoFileData;
|
||||
try
|
||||
{
|
||||
imsAutoFileData = await _imsAutoFileDataRepository.FindByAuctionAssignmentIdAsync(auctionAssignmentId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving IMS Auto File Data by AuctionAssignmentId: {auctionAssignmentId}", auctionAssignmentId);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
|
||||
if (imsAutoFileData == null)
|
||||
{
|
||||
throw new NotFoundException($"IMS Auto File Data id {auctionAssignmentId} not found.");
|
||||
}
|
||||
|
||||
return imsAutoFileData;
|
||||
}
|
||||
|
||||
public async Task<ImsAutoFileData> UpdateAsync(ImsAutoFileData imsAutoFileData)
|
||||
{
|
||||
ImsAutoFileData existingImsAutoFileData = null;
|
||||
try
|
||||
{
|
||||
existingImsAutoFileData = await _imsAutoFileDataRepository.FindByIdAsync(imsAutoFileData.VIN, imsAutoFileData.RepoId);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failed to retreive existingImsAutoFileData with vin: {imsAutoFileData.VIN} and {imsAutoFileData.RepoId}",imsAutoFileData);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
|
||||
|
||||
if (existingImsAutoFileData == null)
|
||||
{
|
||||
throw new NotFoundException($"IMS Auto File Data with id {imsAutoFileData.VIN} / {imsAutoFileData.RepoId} not found.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_imsAutoFileDataRepository.Update(imsAutoFileData);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return (existingImsAutoFileData);
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure updating IMS Auto File Data: {imsAutoFileData}", imsAutoFileData);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure updating IMS Auto File Data: {imsAutoFileData}", imsAutoFileData);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ImsAutoFileData> DeleteAsync(string vin, int repoId)
|
||||
{
|
||||
ImsAutoFileData existingimsAutoFileData = null;
|
||||
try
|
||||
{
|
||||
existingimsAutoFileData = await _imsAutoFileDataRepository.FindByIdAsync(vin, repoId);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failed to retreive existingImsAutoFileData with vin: {vin} and {repoId}");
|
||||
throw new ServerErrorException();
|
||||
|
||||
}
|
||||
|
||||
if (existingimsAutoFileData == null)
|
||||
{
|
||||
throw new NotFoundException($"IMS Auto File Data with id {vin} / {repoId} not found.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_imsAutoFileDataRepository.Remove(existingimsAutoFileData);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return existingimsAutoFileData;
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure deleting IMS Auto File Data with vin: {vin}, repoId: {repoId}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure deleting IMS Auto File Data with vin: {vin}, repoId: {repoId}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Exceptions;
|
||||
using LCU.API.Collections.RepoApi.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Exceptions;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Repositories;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public class ProcessingReceiptService : IProcessingReceiptService
|
||||
{
|
||||
private readonly IProcessingReceiptRepository _repository;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
||||
public ProcessingReceiptService(
|
||||
IProcessingReceiptRepository repository,
|
||||
IUnitOfWork unitOfWork)
|
||||
{
|
||||
_repository = repository;
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<ProcessingReceipt>> FindReceiptsAsync(string accountNo,
|
||||
string accountId = "", string caseId = "")
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _repository.FindReceiptsAsync(accountNo, accountId, caseId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving receipts");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<ProcessingReceipt>> GetReceiptsByAccountNoAsync(string accountNo)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _repository.GetReceiptsByAccountNoAsync(accountNo);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving receipts by accountNo");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<ProcessingReceipt>> GetRetryListAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _repository.GetRetryListAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving retry list of receipts");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SaveAsync(ProcessingReceipt receipt)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _repository.AddOrUpdateAsync(receipt);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure saving receipt");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SaveAllAsync(IReadOnlyCollection<ProcessingReceipt> receipts)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _repository.SaveAllAsync(receipts);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure saving receipts");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+128
@@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.CollectionsApiLib.Models;
|
||||
using LCU.API.Collections.RepoApi.Domain.AlertChecker;
|
||||
using LCU.API.Collections.RepoApi.Domain.Processing;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.Producer
|
||||
{
|
||||
public class AccountAlertCheckService : IAccountAlertCheckService
|
||||
{
|
||||
private const string O4RStatus = "O4R";
|
||||
private const string NoStatus = "";
|
||||
private const string RepoStatus = "REPO";
|
||||
private const string RecaStatus = "RECA";
|
||||
private const string RehoStatus = "REHO";
|
||||
private const string RedeemedStatus = "REDEEMED";
|
||||
private readonly IEventAlertService _eventAlertService;
|
||||
|
||||
private readonly IEnumerable<IAlertChecker> _accountAlertPreStatusCheckers;
|
||||
private readonly IEnumerable<IAlertChecker> _accountAlertPrimaryCheckers;
|
||||
private readonly IEnumerable<IAlertChecker> _accountAlertPrimaryIgnoreWrittenOffCheckers;
|
||||
|
||||
public AccountAlertCheckService( IEventAlertService eventAlertService)
|
||||
{
|
||||
_eventAlertService = eventAlertService;
|
||||
_accountAlertPreStatusCheckers = new List<IAlertChecker>()
|
||||
{
|
||||
new SkipPaidChecker()
|
||||
};
|
||||
_accountAlertPrimaryCheckers = new List<IAlertChecker>()
|
||||
{
|
||||
new RepoNotMovedChecker( 7),
|
||||
new RepoNotSoldChecker( 60, 30),
|
||||
new StatusChangeChecker( RecaStatus),
|
||||
new StatusChangeChecker( RehoStatus),
|
||||
new TimeInStatusChecker( RehoStatus, 30, 30),
|
||||
new StatusChangeChecker( O4RStatus, new List<string>() { NoStatus, RedeemedStatus, RehoStatus, RecaStatus}),
|
||||
new O4rNoRepoChecker(RepoType.Voluntary),
|
||||
new O4rNoRepoChecker(RepoType.Involuntary),
|
||||
new O4rNoRepoChecker(RepoType.PreSkip),
|
||||
new SkipEndChecker( 91),
|
||||
new SkipNotPaidChecker( 106, 15),
|
||||
new DocumentNotFoundChecker("Notice of Plan to Sell", "LTR35", "701", RepoStatus),
|
||||
new DocumentNotFoundChecker("Notice of Repossession", "LTR34", "701", RepoStatus),
|
||||
new RtcLetterNotFoundChecker("RTC Letter", new List<string> { "RTC 1", "RTC 3" }),
|
||||
new SoldNoProceedsChecker(),
|
||||
new AuctionOnHoldChecker(),
|
||||
new AuctionVinDifferentChecker(),
|
||||
new RedemptionEndedNoTitleOrderedChecker()
|
||||
};
|
||||
_accountAlertPrimaryIgnoreWrittenOffCheckers = new List<IAlertChecker>()
|
||||
{
|
||||
new DebitAdjustmentChecker(),
|
||||
new PaymentReceivedChecker(),
|
||||
new CreditAdjustmentChecker(),
|
||||
new SaleFundsReceivedChecker()
|
||||
};
|
||||
}
|
||||
|
||||
public Task AlertChecksPreStatusReject(RepoAccountProcessingSlip slip)
|
||||
{
|
||||
return CheckForAlerts(_accountAlertPreStatusCheckers, slip);
|
||||
}
|
||||
|
||||
public async Task<EventAlert> ManualAlert(EventAlert alert)
|
||||
{
|
||||
return await _eventAlertService.SaveAsync(alert);
|
||||
}
|
||||
|
||||
public async Task MaybeAlertOnInvalidStatus(RepoAccountProcessingSlip slip)
|
||||
{
|
||||
var allowedStatuses = slip.CurrentSettings?.AllowedStatusChanges.Select(x => x.CurrentStatus);
|
||||
if (slip.UpdatedRepoAccount != null && allowedStatuses != null &&
|
||||
!allowedStatuses.Contains(slip.UpdatedRepoAccount.Status))
|
||||
{
|
||||
await _eventAlertService.SaveAsync(new EventAlert()
|
||||
{
|
||||
RepoId = slip.UpdatedRepoAccount.RepoId,
|
||||
AccountNo = slip.UpdatedRepoAccount.AccountNo,
|
||||
CreatedBy = "Importer",
|
||||
DateAdded = DateTime.Now,
|
||||
Message = $"Collections System Status {slip.UpdatedRepoAccount.Status} Not Valid"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public async Task AlertChecksPrimary( RepoAccountProcessingSlip slip)
|
||||
{
|
||||
if (slip.TransactionHistory != null && slip.TransactionHistory.Any())
|
||||
{
|
||||
await CheckForAlerts(_accountAlertPrimaryIgnoreWrittenOffCheckers, slip);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"No Transactions Available, skipping Transaction Alerts for {slip.AccountNo}");
|
||||
}
|
||||
|
||||
await CheckForAlerts(_accountAlertPrimaryCheckers, slip);
|
||||
}
|
||||
|
||||
private async Task CheckForAlerts(IEnumerable<IAlertChecker> alertCheckers, RepoAccountProcessingSlip slip)
|
||||
{
|
||||
var eventAlerts = new List<EventAlert>();
|
||||
foreach (var alertChecker in alertCheckers)
|
||||
{
|
||||
try
|
||||
{
|
||||
var createdAlerts = alertChecker.CheckForAlert(slip);
|
||||
if (createdAlerts != null)
|
||||
{
|
||||
eventAlerts.AddRange(createdAlerts);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var accountNumber = slip.AccountNo ?? "Account Number Not Found";
|
||||
Log.Error(ex, $"Error Checking Alert {alertChecker.GetType()} For Account: {accountNumber}");
|
||||
}
|
||||
}
|
||||
|
||||
await _eventAlertService.SaveRangeAsync(eventAlerts);
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Domain.Processing;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.Producer
|
||||
{
|
||||
public interface IAccountAlertCheckService
|
||||
{
|
||||
/**
|
||||
* These alerts run before the account is checked to see if it was moved to an un-allowed status.
|
||||
* What is this moves the status to an un-allowed status?
|
||||
*/
|
||||
Task AlertChecksPreStatusReject(RepoAccountProcessingSlip slip);
|
||||
|
||||
/**
|
||||
* These alerts run after the account has been updated or created and is the primary set of alert checks.
|
||||
*/
|
||||
Task AlertChecksPrimary(RepoAccountProcessingSlip slip);
|
||||
|
||||
Task<EventAlert> ManualAlert(EventAlert alert);
|
||||
Task MaybeAlertOnInvalidStatus(RepoAccountProcessingSlip processingSlip);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Domain.Processing;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Producer;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.Producer
|
||||
{
|
||||
public interface IRepoAccountProcessor
|
||||
{
|
||||
Task<RepoProcessingResult> HandleAccount(ProcessingItem item);
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
using System.Collections.Generic;
|
||||
using LCU.API.Collections.CollectionsApiLib.Models;
|
||||
using LCU.API.Collections.RepoApi.Domain.Processing;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Consumer;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Settings;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.Producer
|
||||
{
|
||||
public interface IRepoAccountProcessorService
|
||||
{
|
||||
RepoAccount CreateRepoAccount(TemenosAccount account, TemenosCase recoveryCase, LoanDetail detail);
|
||||
void CheckForRejectedStatusChange(RepoAccount originalRepoAccount, RepoAccount updatedRepoAccount, Settings settings);
|
||||
RepoAccount UpdateRepoAccountFromParts(RepoAccountProcessingSlip slip);
|
||||
void UpdateRepoAccountDateClosed(RepoAccount repoAccount);
|
||||
void TryUpdateCollateralRetitleDate(RepoAccount repo, TemenosCase temenosCase);
|
||||
void AddAccountUpdateRecord(RepoAccountProcessingSlip slip);
|
||||
RepoCompanySelection CreateNewRepoCompanyAssignment(Collateral collateral, TemenosCase temenosCase, int? newRepoCompanyId);
|
||||
void TryUpdateOrCreateRepoCompanyAssignment(RepoAccount repoAccount,
|
||||
TemenosCase temenosCase, Settings settings);
|
||||
int? MapRepoAgentIdToRepoCompanyId(string repoAgentId, IReadOnlyCollection<RepoCompany> repoCompanies);
|
||||
}
|
||||
}
|
||||
+331
@@ -0,0 +1,331 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.DataAccess;
|
||||
using LCU.API.Collections.RepoApi.Domain.Extensions;
|
||||
using LCU.API.Collections.RepoApi.Domain.Processing;
|
||||
using LCU.API.Collections.RepoApi.Exceptions;
|
||||
using LCU.API.Collections.RepoApi.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Producer;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.Producer
|
||||
{
|
||||
public class RepoAccountProcessor : IRepoAccountProcessor
|
||||
{
|
||||
private readonly IRepoAccountProcessorService _repoAccountProcessorService;
|
||||
private readonly IAccountAlertCheckService _accountAlertCheckService;
|
||||
private readonly IEmailer _emailer;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ICollectionsDao _collectionsDao;
|
||||
private readonly ICoreDao _coreDao;
|
||||
private readonly IRepoAccountService _repoAccountService;
|
||||
private readonly IRepoLetterService _repoLetterService;
|
||||
private readonly IAuctionAssignmentService _auctionAssignmentService;
|
||||
public const string UserStatusFieldOld = "User Status Code 1";
|
||||
public const int CaseTypeRepo = 7; // Source: collections API configuration call
|
||||
private const string O4RStatus = "O4R";
|
||||
private const string NoStatus = "";
|
||||
private const string RepoStatus = "REPO";
|
||||
private const string RecaStatus = "RECA";
|
||||
private const string RehoStatus = "REHO";
|
||||
private const string RedeemedStatus = "REDEEMED";
|
||||
private const string TemenosQueuedBy = "Temenos";
|
||||
|
||||
public RepoAccountProcessor(
|
||||
IRepoAccountProcessorService repoAccountProcessorService,
|
||||
IAccountAlertCheckService accountAlertCheckService,
|
||||
ICollectionsDao collectionsDao,
|
||||
IEmailer emailer,
|
||||
ISettingsService settingsService,
|
||||
IRepoAccountService repoAccountService,
|
||||
IRepoLetterService repoLetterService, ICoreDao coreDao,
|
||||
IAuctionAssignmentService auctionAssignmentService)
|
||||
{
|
||||
_repoAccountProcessorService = repoAccountProcessorService;
|
||||
_accountAlertCheckService = accountAlertCheckService;
|
||||
_collectionsDao = collectionsDao;
|
||||
_emailer = emailer;
|
||||
_settingsService = settingsService;
|
||||
_repoAccountService = repoAccountService;
|
||||
_repoLetterService = repoLetterService;
|
||||
_coreDao = coreDao;
|
||||
_auctionAssignmentService = auctionAssignmentService;
|
||||
}
|
||||
|
||||
public async Task<RepoProcessingResult> HandleAccount(ProcessingItem item)
|
||||
{
|
||||
var processingSlip = await CreateProcessingSlip(item);
|
||||
if (IsAutoCloseAccount(processingSlip))
|
||||
{
|
||||
Log.Debug($"Auto-Closing account {processingSlip.AccountNo} due to no Temenos Account.");
|
||||
await AutoCloseAccount(processingSlip);
|
||||
return RepoProcessingResult.Autoclosed;
|
||||
}
|
||||
|
||||
processingSlip.AssertReadyForPreChecks();
|
||||
|
||||
// 9640 Attempt to pre-check for a matching status when case is missing.
|
||||
// If no found case, and the status hasn't changed; instead of failing just skip the unconverted account.
|
||||
if (IsUnconvertedIntellectAccount(processingSlip))
|
||||
{
|
||||
Log.Debug($"Repo Processor: {item.AccountNo} is not updated for Temenos; skipping.");
|
||||
return RepoProcessingResult.Unconverted;
|
||||
}
|
||||
|
||||
processingSlip.RecoveryCase = processingSlip.RecoveryAccount.FindRecoveryCaseOrDefault(processingSlip.CaseId);
|
||||
|
||||
if (!IsPossiblyRepossessionCase(processingSlip))
|
||||
{
|
||||
return RepoProcessingResult.Ineligible;
|
||||
}
|
||||
|
||||
processingSlip.AssertReadyForFullProcessing();
|
||||
|
||||
if (ShouldCreateNewAccount(processingSlip))
|
||||
{
|
||||
Log.Debug($"Creating new repo account for {item.AccountNo} : {item.CaseId} ");
|
||||
processingSlip.UpdatedRepoAccount = await CreateNewRepoAccount(processingSlip);
|
||||
await _accountAlertCheckService.MaybeAlertOnInvalidStatus(processingSlip);
|
||||
processingSlip = await AddAlertDataToProcessingSlip(processingSlip);
|
||||
processingSlip.AssertReadyForAlertChecks();
|
||||
await _accountAlertCheckService.AlertChecksPrimary(processingSlip);
|
||||
return RepoProcessingResult.Created;
|
||||
}
|
||||
|
||||
if (!processingSlip.ExistingRepoAccount.HasAnyCollateral())
|
||||
{
|
||||
await _emailer.MaybeSendO4rNoCollateralEmail(processingSlip.ExistingRepoAccount);
|
||||
throw new MissingRequirementException("Existing Account Collateral",
|
||||
$"{processingSlip.AccountNo} did not have collateral on existing account.");
|
||||
}
|
||||
|
||||
Log.Debug($"Updating Repo account for {item.AccountNo} : {item.CaseId}");
|
||||
processingSlip.UpdatedRepoAccount = await UpdateExistingRepoAccount(processingSlip);
|
||||
|
||||
//Add check for recent account repo closing when in RECA status to prevent reprocessing and new repo account creation with a DateClosed date = null
|
||||
//The DateClosed date = null will ensure the account gets reprocessed, we don't want alert checks with these accounts
|
||||
|
||||
if (processingSlip.ExistingRepoAccount.DateClosed == null && processingSlip.UpdatedRepoAccount.DateClosed != null)
|
||||
{
|
||||
return RepoProcessingResult.Updated;
|
||||
}
|
||||
|
||||
processingSlip = await AddAlertDataToProcessingSlip(processingSlip);
|
||||
processingSlip.AssertReadyForAlertChecks();
|
||||
await _accountAlertCheckService.AlertChecksPrimary(processingSlip);
|
||||
return RepoProcessingResult.Updated;
|
||||
}
|
||||
|
||||
// New method to give a chance to filter out non-repo cases and mark ineligible
|
||||
private bool IsPossiblyRepossessionCase(RepoAccountProcessingSlip slip)
|
||||
{
|
||||
if (slip.RecoveryCase == null) return true;
|
||||
try
|
||||
{
|
||||
if (slip.RecoveryCase.CaseTypeId != CaseTypeRepo)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
StatusService.ConvertStatus(slip.RecoveryCase.RepoStatus);
|
||||
}
|
||||
catch (IneligibleAccountException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task AutoCloseAccount(RepoAccountProcessingSlip processingSlip)
|
||||
{
|
||||
AppUser appUser = new AppUser()
|
||||
{
|
||||
Email = processingSlip.RequestedBy.QueuedBy,
|
||||
FirstName = "System",
|
||||
LastName = "System"
|
||||
};
|
||||
processingSlip.ExistingRepoAccount.DateClosed = DateTime.Now;
|
||||
processingSlip.ExistingRepoAccount.AccountLastProcessed = DateTime.Now;
|
||||
await _repoAccountService.UpdateAsync(processingSlip.ExistingRepoAccount, appUser);
|
||||
}
|
||||
|
||||
private bool IsAutoCloseAccount(RepoAccountProcessingSlip processingSlip)
|
||||
{
|
||||
// If recovery account couldn't be fetched, that would have been an exception.
|
||||
// If recovery account is null, it means "not there", so close the RepoAccount.
|
||||
// This means close the RepoAccount because the account
|
||||
// was likely not brought over from Intellect. If something DOES happen, just create
|
||||
// a new account later....
|
||||
return (processingSlip.RecoveryAccount == null
|
||||
&& processingSlip.ExistingRepoAccount != null
|
||||
&& processingSlip.ExistingRepoAccount.DateClosed == null);
|
||||
}
|
||||
|
||||
public async Task<RepoAccountProcessingSlip> CreateProcessingSlip(ProcessingItem item)
|
||||
{
|
||||
var accountNo = VerifyAccountNo(item);
|
||||
|
||||
var slip = new RepoAccountProcessingSlip
|
||||
{
|
||||
AccountNo = accountNo,
|
||||
CurrentSettings = await _settingsService.GetCachedRepoSettingsAsync(),
|
||||
RecoveryAccount = await _collectionsDao.RetrieveAccountAsync(accountNo),
|
||||
ExistingRepoAccount = await _repoAccountService.GetNewestOpenAccount(accountNo),
|
||||
Loan = await _coreDao.RetrieveLoanAsync(accountNo),
|
||||
CaseId = VerifyCaseId(item.CaseId),
|
||||
RequestedBy = item
|
||||
};
|
||||
|
||||
if (slip.RecoveryAccount != null)
|
||||
{
|
||||
slip.Letters = await _repoLetterService.ListByAccountIdAsync(slip.RecoveryAccount.AccountId);
|
||||
}
|
||||
|
||||
return slip;
|
||||
}
|
||||
|
||||
public async Task<RepoAccountProcessingSlip> AddAlertDataToProcessingSlip(RepoAccountProcessingSlip slip)
|
||||
{
|
||||
slip.TransactionHistory = await _coreDao.RetrieveTransactionsAsync(slip.AccountNo);
|
||||
slip.Member = await _coreDao.RetrieveMemberAsync(slip.AccountNo);
|
||||
var uniqueIds = slip.UpdatedRepoAccount.GetUniqueAuctionAssignmentIds();
|
||||
slip.AuctionAssignments = await _auctionAssignmentService.GetListByIdsAsync(uniqueIds);
|
||||
return slip;
|
||||
}
|
||||
|
||||
public bool IsUnconvertedIntellectAccount(RepoAccountProcessingSlip repoAccountSlip)
|
||||
{
|
||||
var recoveryAccount = repoAccountSlip.RecoveryAccount;
|
||||
var caseId = repoAccountSlip.CaseId;
|
||||
var originalRepoAccount = repoAccountSlip.ExistingRepoAccount;
|
||||
if (!recoveryAccount.HasRecoveryCase(caseId) && recoveryAccount.HasOldIntellectStatus())
|
||||
{
|
||||
Log.Information(
|
||||
$"Repo Processor: {repoAccountSlip.AccountNo} has no case, but user status code. Running pre-check.");
|
||||
return (originalRepoAccount.Status == recoveryAccount.CustomFieldMap[UserStatusFieldOld]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public int VerifyCaseId(string itemCaseId)
|
||||
{
|
||||
try
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(itemCaseId) ? 0 : Int32.Parse(itemCaseId);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new IneligibleAccountException(
|
||||
$"Bad CaseId {itemCaseId}",
|
||||
$"The CaseId is present, but in a non-integer format.");
|
||||
}
|
||||
}
|
||||
|
||||
public string VerifyAccountNo(ProcessingItem item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new IneligibleAccountException("Processing Item", "The processing item was null.");
|
||||
}
|
||||
|
||||
if (item.AccountNo == null)
|
||||
{
|
||||
throw new IneligibleAccountException(
|
||||
"Null AccountNo",
|
||||
"There should never be null accounts on the queue.");
|
||||
}
|
||||
|
||||
// if (Regex.IsMatch(item.AccountNo, "^[a-zA-Z0-9]*$"))
|
||||
// {
|
||||
// throw new IneligibleAccountException(
|
||||
// "AccountNo",
|
||||
// "AccountNo contains alphabetical or symbol characters");
|
||||
// }
|
||||
|
||||
return item.AccountNo;
|
||||
}
|
||||
|
||||
public async Task<RepoAccount> UpdateExistingRepoAccount(RepoAccountProcessingSlip slip)
|
||||
{
|
||||
AppUser appUser = new AppUser()
|
||||
{
|
||||
Email = slip.RequestedBy.QueuedBy,
|
||||
FirstName = "System",
|
||||
LastName = "System"
|
||||
};
|
||||
|
||||
if (ShouldRunCompleteUpdate(slip))
|
||||
{
|
||||
RepoAccount updatedRepoAccount =
|
||||
_repoAccountProcessorService.UpdateRepoAccountFromParts(slip);
|
||||
await _accountAlertCheckService.AlertChecksPreStatusReject(slip);
|
||||
|
||||
_repoAccountProcessorService.CheckForRejectedStatusChange(slip.ExistingRepoAccount, updatedRepoAccount,
|
||||
slip.CurrentSettings);
|
||||
_repoAccountProcessorService.TryUpdateOrCreateRepoCompanyAssignment(updatedRepoAccount,
|
||||
slip.RecoveryCase, slip.CurrentSettings);
|
||||
_repoAccountProcessorService.TryUpdateCollateralRetitleDate(updatedRepoAccount, slip.RecoveryCase);
|
||||
await _repoAccountService.UpdateAsync(updatedRepoAccount, appUser);
|
||||
|
||||
return updatedRepoAccount;
|
||||
}
|
||||
|
||||
_repoAccountProcessorService.TryUpdateOrCreateRepoCompanyAssignment(slip.ExistingRepoAccount,
|
||||
slip.RecoveryCase, slip.CurrentSettings);
|
||||
_repoAccountProcessorService.AddAccountUpdateRecord(slip);
|
||||
_repoAccountProcessorService.TryUpdateCollateralRetitleDate(slip.ExistingRepoAccount, slip.RecoveryCase);
|
||||
_repoAccountProcessorService.UpdateRepoAccountDateClosed(slip.ExistingRepoAccount);
|
||||
await _repoAccountService.UpdateAsync(slip.ExistingRepoAccount, appUser);
|
||||
return slip.ExistingRepoAccount;
|
||||
}
|
||||
|
||||
public bool ShouldRunCompleteUpdate(RepoAccountProcessingSlip slip)
|
||||
{
|
||||
if (slip.ExistingRepoAccount == null)
|
||||
{
|
||||
throw new MissingRequirementException("Existing Repo Account",
|
||||
$"Existing Repo Account required for update check, {slip.AccountNo}");
|
||||
}
|
||||
|
||||
var newStatus = StatusService.ConvertStatus(slip.RecoveryCase.RepoStatus);
|
||||
|
||||
var dateLastPaid = slip.RecoveryAccount.DateLastPaid != null
|
||||
? DateTime.Parse(slip.RecoveryAccount.DateLastPaid)
|
||||
: slip.ExistingRepoAccount.LastPaymentDate;
|
||||
var refStatusChanged = slip.RecoveryAccount.AccountStatus != slip.ExistingRepoAccount.ReferenceStatus;
|
||||
|
||||
// This is a special case, it picked up the written off flag, before there was a written off date.
|
||||
if (string.Equals(slip.ExistingRepoAccount.ReferenceStatus, RepoAccountProcessorService.WrittenOff,
|
||||
StringComparison.CurrentCultureIgnoreCase)
|
||||
&& slip.ExistingRepoAccount.WrittenOffDate == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var needsRedemptionExpired = slip.ExistingRepoAccount.RedemptionExpiredDate == null
|
||||
&& (slip.RecoveryCase.RedemptionExpiredDateIl != null ||
|
||||
slip.RecoveryCase.RedemptionExpiredDateWi != null);
|
||||
|
||||
return !string.Equals(newStatus, slip.ExistingRepoAccount.Status, StringComparison.CurrentCultureIgnoreCase)
|
||||
|| slip.ExistingRepoAccount.LastPaymentDate != dateLastPaid ||
|
||||
slip.ExistingRepoAccount.Balance != slip.RecoveryAccount.Balance
|
||||
|| needsRedemptionExpired || refStatusChanged;
|
||||
}
|
||||
|
||||
public async Task<RepoAccount> CreateNewRepoAccount(RepoAccountProcessingSlip slip)
|
||||
{
|
||||
var repoAccount = _repoAccountProcessorService.CreateRepoAccount(slip.RecoveryAccount, slip.RecoveryCase, slip.Loan);
|
||||
var newRepoAccount = await _repoAccountService.SaveAsync(repoAccount);
|
||||
return newRepoAccount;
|
||||
}
|
||||
|
||||
public bool ShouldCreateNewAccount(RepoAccountProcessingSlip slip)
|
||||
{
|
||||
return slip != null && (slip.ExistingRepoAccount == null || slip.ExistingRepoAccount.DateClosed != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
+585
@@ -0,0 +1,585 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using LCU.API.Collections.CollectionsApiLib.Models;
|
||||
using LCU.API.Collections.RepoApi.DataAccess;
|
||||
using LCU.API.Collections.RepoApi.Domain.Processing;
|
||||
using LCU.API.Collections.RepoApi.Exceptions;
|
||||
using LCU.API.Collections.RepoApi.Models.Results;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Consumer;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Settings;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.Producer
|
||||
{
|
||||
public class RepoAccountProcessorService : IRepoAccountProcessorService
|
||||
{
|
||||
private const string RedeemedStatus = "REDEEMED";
|
||||
private const string PaidSkipStatus = "PSKP";
|
||||
private const string RepoCancelledStatus = "RECA";
|
||||
public const string WrittenOff = "Written Off";
|
||||
public const string SixtyPLusUserAssignedLabel = "SixtyPlus User Assigned";
|
||||
private readonly ICoreDao _coreDao;
|
||||
private readonly IRedemptionExpiredDateCalculator _redemptionExpiredDateCalculator;
|
||||
private readonly IAccountAlertCheckService _accountAlertCheckService;
|
||||
private readonly IEventAlertService _eventAlertService;
|
||||
private readonly IRepoCompanySelectionService _repoCompanySelectionService;
|
||||
|
||||
public RepoAccountProcessorService(ICoreDao coreDao,
|
||||
IRedemptionExpiredDateCalculator redemptionExpiredDateCalculator,
|
||||
IAccountAlertCheckService accountAlertCheckService,
|
||||
IEventAlertService eventAlertService, IRepoCompanySelectionService repoCompanySelectionService)
|
||||
{
|
||||
_coreDao = coreDao;
|
||||
_redemptionExpiredDateCalculator = redemptionExpiredDateCalculator;
|
||||
_accountAlertCheckService = accountAlertCheckService;
|
||||
_eventAlertService = eventAlertService;
|
||||
_repoCompanySelectionService = repoCompanySelectionService;
|
||||
}
|
||||
|
||||
// This process determined from the original batch code. (LCU.CollectionRepossessionTracking::IntellecRepoImport)
|
||||
// -> A) Get XP/Core/Apex Account
|
||||
// -> B) Get XP/Core/Apex Loan
|
||||
// -> B.1) Fields needed: Branch, Next due date
|
||||
// ---> If it can't find a loan, it does nothing... what do we WANT do?
|
||||
// -> C) Get XP/Core/Apex Loan Collateral
|
||||
// ---> If there is no collateral, it does nothing... what do we WANT do?
|
||||
// -> D) Get XP/Core/Apex Borrowers (Individuals)
|
||||
// ---> No borrowers throws an exception? ... what do we WANT do?
|
||||
// -> E) Create Repo Account from Loan, Collateral, Borrowers and Temenos Account
|
||||
public RepoAccount CreateRepoAccount(TemenosAccount account, TemenosCase recoveryCase, LoanDetail loanDetail)
|
||||
{
|
||||
var collateralDetailRequest = _coreDao.RetrieveCollateralAsync(account.AccountIdentifier);
|
||||
|
||||
var collateralDetail = collateralDetailRequest.GetAwaiter().GetResult();
|
||||
if (collateralDetail == null || !collateralDetail.Any())
|
||||
{
|
||||
throw new AccountCreationFailed(
|
||||
$"Repo-Account Not Created using Account with AccountNo: {account.AccountIdentifier}. No Collateral.");
|
||||
}
|
||||
|
||||
var borrowers = ConstructRepoAccountBorrowers(loanDetail);
|
||||
if (borrowers == null || !borrowers.Any())
|
||||
{
|
||||
throw new AccountCreationFailed(
|
||||
$"Repo-Account Not Created using Account with AccountNo: {account.AccountIdentifier}. No Borrowers.");
|
||||
}
|
||||
|
||||
var repoAccount = ConstructRepoAccountFromParts(loanDetail, account, recoveryCase);
|
||||
repoAccount.Borrowers = borrowers;
|
||||
repoAccount.Collaterals = ConstructCollateralFromDetails(collateralDetail);
|
||||
TryUpdateCollateralRetitleDate(repoAccount, recoveryCase);
|
||||
|
||||
Log.Information($"Created Repo-Account for Not-Found Account. AccountNo: {account.AccountIdentifier}");
|
||||
return repoAccount;
|
||||
}
|
||||
|
||||
public List<Borrower> ConstructRepoAccountBorrowers(LoanDetail coreAccount)
|
||||
{
|
||||
List<Borrower> borrowers = new List<Borrower>();
|
||||
try
|
||||
{
|
||||
foreach (var coreAccountBorrower in coreAccount.Borrowers)
|
||||
{
|
||||
var borrower = new Borrower()
|
||||
{
|
||||
IndividualName = coreAccountBorrower.Name,
|
||||
IndividualId = coreAccountBorrower.IndividualId.ToString(),
|
||||
OwnershipType = (int)ConvertBorrowerType(coreAccountBorrower.Type)
|
||||
};
|
||||
borrowers.Add(borrower);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error in ConstructRepoAccountBorrowers");
|
||||
}
|
||||
|
||||
return borrowers;
|
||||
}
|
||||
|
||||
public List<Collateral> ConstructCollateralFromDetails(IReadOnlyCollection<CollateralDetail> collateralDetail)
|
||||
{
|
||||
List<Collateral> collateralList = new List<Collateral>();
|
||||
foreach (var detail in collateralDetail)
|
||||
{
|
||||
var collateral = new Collateral()
|
||||
{
|
||||
BodyStyle = null,
|
||||
CollateralTypeId = null,
|
||||
Description = detail.Description,
|
||||
Inoperable = null,
|
||||
RefCollateralId = detail.CollateralId.ToString()
|
||||
};
|
||||
if (detail.Other != null)
|
||||
{
|
||||
collateral.Vin = detail.Other.MiscellaneousInformation;
|
||||
}
|
||||
|
||||
if (detail.Vehicle != null)
|
||||
{
|
||||
collateral.Make = detail.Vehicle.Make;
|
||||
collateral.Model = detail.Vehicle.Model;
|
||||
collateral.Vin = detail.Vehicle.VIN;
|
||||
collateral.Year = detail.Vehicle.Year;
|
||||
collateral.BodyStyle = detail.Vehicle.BodyStyle;
|
||||
}
|
||||
|
||||
collateralList.Add(collateral);
|
||||
}
|
||||
|
||||
return collateralList;
|
||||
}
|
||||
|
||||
public RepoAccount ConstructRepoAccountFromParts(LoanDetail coreAccount, TemenosAccount account,
|
||||
TemenosCase recoveryCase)
|
||||
{
|
||||
DateTime? lastPaymentDate = null;
|
||||
DateTime? dateFirstDelinquent = null;
|
||||
|
||||
var lastPaymentResult = GetDateTimeFromString(account.DateLastPaid);
|
||||
if (lastPaymentResult.IsSuccess)
|
||||
{
|
||||
lastPaymentDate = lastPaymentResult.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning(
|
||||
$"Construct Repo Account from temenos accountNo: {account.AccountNumber}. Could not parse last payment result : {account.DateLastPaid}.",
|
||||
lastPaymentResult, account);
|
||||
}
|
||||
|
||||
|
||||
var dateFirstDelinquentResult = GetDateTimeFromString(account.DateFirstDelinquent);
|
||||
if (dateFirstDelinquentResult.IsSuccess)
|
||||
{
|
||||
dateFirstDelinquent = dateFirstDelinquentResult.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning(
|
||||
$"Construct Repo Account from temenos accountNo: {account.AccountNumber}. failed parsing dateFirstDelinquent {account.DateFirstDelinquent}",
|
||||
dateFirstDelinquentResult, account);
|
||||
// This is no longer a "MissingRequirementException", per Bill Johns.
|
||||
}
|
||||
|
||||
var sixtyPlusUserAssignedNumber = GetSixtyPlusUserNumber(account);
|
||||
|
||||
var repoAccount = new RepoAccount()
|
||||
{
|
||||
DueToPayDate = coreAccount.NextDueDate,
|
||||
Balance = account.Balance,
|
||||
AccountTypeCode = account.AccountType.Code,
|
||||
OrigAccountTypeCode = account.AccountType.Code,
|
||||
LoanNo = long.Parse(account.AccountIdentifier),
|
||||
MemberNo = MemberIdFromAccountId(account.AccountIdentifier),
|
||||
Branch = account.Branch != null ? int.Parse(account.Branch) : 0,
|
||||
AccountNo = account.AccountIdentifier,
|
||||
Status = StatusService.ConvertStatus(recoveryCase.RepoStatus),
|
||||
O4RDate = recoveryCase.CreatedDate,
|
||||
Collector = account.LoanOfficer,
|
||||
DateFirstDelinquent = dateFirstDelinquent,
|
||||
DelinquentAmount = account.DQAmount,
|
||||
CurrentDelinquentAmount = account.DQAmount,
|
||||
SubStatus = "",
|
||||
DaysDelinquent = account.DaysDq,
|
||||
LastPaymentDate = lastPaymentDate,
|
||||
LstPmtAmt = account.AmountLastPaid,
|
||||
OrigAmount = account.OriginalLoanAmount,
|
||||
RequiredPaymentAmount = account.DQAmount,
|
||||
PrimaryIndividualId = coreAccount.IndividualId[0].ToString(),
|
||||
Bankruptcy = $"{account.BankruptcyFlag}",
|
||||
DateFirstIntellect = recoveryCase.CreatedDate,
|
||||
ReferenceId = account.AccountId,
|
||||
ReferenceCaseId = recoveryCase.CaseId,
|
||||
AccountLastProcessed = DateTime.Now,
|
||||
ReferenceStatus = account.AccountStatus,
|
||||
WrittenOffDate = coreAccount.Status == WrittenOff ? coreAccount.WrittenOffDate : (DateTime?)null,
|
||||
SixtyPlusCollectorId = sixtyPlusUserAssignedNumber
|
||||
};
|
||||
|
||||
return repoAccount;
|
||||
}
|
||||
|
||||
public long MemberIdFromAccountId(string accountId)
|
||||
{
|
||||
return _coreDao.RetrieveMemberAsync(accountId).GetAwaiter().GetResult().MemberId;
|
||||
}
|
||||
|
||||
public RepoAccount UpdateRepoAccountFromParts(RepoAccountProcessingSlip slip)
|
||||
{
|
||||
RepoAccount originalRepoAccount = slip.ExistingRepoAccount;
|
||||
TemenosAccount account = slip.RecoveryAccount;
|
||||
TemenosCase recoveryCase = slip.RecoveryCase;
|
||||
IReadOnlyCollection<RepoLetter> letters = slip.Letters;
|
||||
var redemptionExpiredDate = _redemptionExpiredDateCalculator.CalculateAsync(recoveryCase, letters);
|
||||
|
||||
DateTime? lastPaymentDate = null;
|
||||
DateTime? dateFirstDelinquent = originalRepoAccount.DateFirstDelinquent;
|
||||
|
||||
var lastPaymentResult = GetDateTimeFromString(account.DateLastPaid);
|
||||
if (lastPaymentResult.IsSuccess)
|
||||
{
|
||||
lastPaymentDate = lastPaymentResult.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning(
|
||||
$"Repo account {originalRepoAccount.RepoId} ({originalRepoAccount.AccountNo}). Could not parse last payment result : {account.DateLastPaid}.",
|
||||
lastPaymentResult);
|
||||
}
|
||||
|
||||
var dateFirstDelinquentResult = GetDateTimeFromString(account.DateFirstDelinquent);
|
||||
if (dateFirstDelinquentResult.IsSuccess)
|
||||
{
|
||||
dateFirstDelinquent = dateFirstDelinquentResult.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error(
|
||||
$"Repo account {originalRepoAccount.RepoId} ({originalRepoAccount.AccountNo}) failed parsing dateFirstDelinquent {account.DateFirstDelinquent}",
|
||||
dateFirstDelinquentResult);
|
||||
// This is no longer a "MissingRequirementException", per Bill Johns.
|
||||
}
|
||||
|
||||
var sixtyPlusUserAssignedNumber = GetSixtyPlusUserNumber(account);
|
||||
|
||||
var repoAccount = new RepoAccount()
|
||||
{
|
||||
RepoId = originalRepoAccount.RepoId,
|
||||
DueToPayDate = account.DueDate,
|
||||
Balance = account.Balance,
|
||||
AccountTypeCode = account.AccountType.Code,
|
||||
OrigAccountTypeCode = originalRepoAccount.OrigAccountTypeCode,
|
||||
LoanNo = originalRepoAccount.LoanNo,
|
||||
MemberNo = originalRepoAccount.MemberNo,
|
||||
Branch = originalRepoAccount.Branch,
|
||||
AccountNo = originalRepoAccount.AccountNo,
|
||||
Status = StatusService.ConvertStatus(recoveryCase.RepoStatus),
|
||||
O4RDate = originalRepoAccount.O4RDate,
|
||||
Collector = account.LoanOfficer,
|
||||
DateFirstDelinquent = dateFirstDelinquent,
|
||||
DelinquentAmount = account.DQAmount,
|
||||
CurrentDelinquentAmount = account.DQAmount,
|
||||
SubStatus = "",
|
||||
DaysDelinquent = originalRepoAccount.DaysDelinquent, //leave this set to days delinquent when added.
|
||||
LastPaymentDate = lastPaymentDate,
|
||||
LstPmtAmt = account.AmountLastPaid ?? 0,
|
||||
OrigAmount = account.OriginalLoanAmount ?? 0,
|
||||
RedemptionExpiredDate = redemptionExpiredDate,
|
||||
RequiredPaymentAmount = account.DQAmount,
|
||||
PrimaryIndividualId = originalRepoAccount.PrimaryIndividualId,
|
||||
Bankruptcy = $"{account.BankruptcyFlag}",
|
||||
Collaterals = originalRepoAccount.Collaterals,
|
||||
RepoStatusChanges = originalRepoAccount.RepoStatusChanges,
|
||||
ManualRedemptionDate = originalRepoAccount.ManualRedemptionDate,
|
||||
DateFirstIntellect = originalRepoAccount.DateFirstIntellect,
|
||||
Borrowers = originalRepoAccount.Borrowers,
|
||||
PaidSkipAmount = originalRepoAccount.PaidSkipAmount,
|
||||
RefundStatus = originalRepoAccount.RefundStatus,
|
||||
ReferenceId = account.AccountId,
|
||||
ReferenceCaseId = recoveryCase.CaseId,
|
||||
AccountLastProcessed = DateTime.Now,
|
||||
ReferenceStatus = account.AccountStatus,
|
||||
WrittenOffDate = slip.Loan.Status == WrittenOff ? slip.Loan.WrittenOffDate : (DateTime?)null,
|
||||
SixtyPlusCollectorId = sixtyPlusUserAssignedNumber
|
||||
};
|
||||
|
||||
return repoAccount;
|
||||
}
|
||||
|
||||
/**
|
||||
* High Risk function because of string comparison and unknown mapping.
|
||||
* The old system got the integer from the database.
|
||||
* The new system gets a string from the web service.
|
||||
* This maps the new string to the old integer, if borrower types start
|
||||
* showing up wrong, look here...
|
||||
*/
|
||||
public OwnershipType ConvertBorrowerType(string type)
|
||||
{
|
||||
if (type != null && string.Equals(type.Trim(), "Borrower", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return OwnershipType.Borrower;
|
||||
}
|
||||
|
||||
if (type != null && string.Equals(type.Trim(), "Co-Borrower", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return OwnershipType.Coborrower;
|
||||
}
|
||||
|
||||
return OwnershipType.Other;
|
||||
}
|
||||
|
||||
public void CheckForRejectedStatusChange(RepoAccount originalRepoAccount, RepoAccount updatedRepoAccount,
|
||||
Settings settings)
|
||||
{
|
||||
if (!IsStatusMatch(originalRepoAccount, updatedRepoAccount.Status) &&
|
||||
!IsStatusChangeAllowed(originalRepoAccount.Status, updatedRepoAccount.Status,
|
||||
settings.AllowedStatusChanges))
|
||||
{
|
||||
Log.Warning(
|
||||
$"Status Change Reverted for Account {updatedRepoAccount.AccountNo} from {originalRepoAccount.Status} to {updatedRepoAccount.Status}");
|
||||
|
||||
_accountAlertCheckService.ManualAlert(new EventAlert()
|
||||
{
|
||||
RepoId = updatedRepoAccount.RepoId,
|
||||
AccountNo = updatedRepoAccount.AccountNo,
|
||||
CreatedBy = "Importer",
|
||||
DateAdded = DateTime.Now,
|
||||
Message = "Collections System Status Does Not Match Repo Management",
|
||||
SubMessage =
|
||||
$"Invalid Status Change from {originalRepoAccount.Status} to {updatedRepoAccount.Status}"
|
||||
});
|
||||
updatedRepoAccount.Status = originalRepoAccount.Status;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsStatusChangeAllowed(string oldStatus, string newStatus,
|
||||
IEnumerable<AllowedStatusChange> settingsAllowedStatusChanges)
|
||||
{
|
||||
foreach (var allowedStatusChange in settingsAllowedStatusChanges)
|
||||
{
|
||||
if (IsStringMatch(allowedStatusChange.CurrentStatus, oldStatus) &&
|
||||
IsStringMatch(allowedStatusChange.AllowedChangeStatus, newStatus))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsStatusMatch(RepoAccount repo, string status)
|
||||
{
|
||||
if (repo?.Status == null || status == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return string.Equals(repo.Status.Trim(), status.Trim(), StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public bool IsStringMatch(string x, string y)
|
||||
{
|
||||
if (x == null || y == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return string.Equals(x.Trim(), y.Trim(), StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public void UpdateRepoAccountDateClosed(RepoAccount repoAccount)
|
||||
{
|
||||
// Close the account after four days in certain statuses
|
||||
if ((repoAccount.Status == PaidSkipStatus || repoAccount.Status == RedeemedStatus || repoAccount.Status == RepoCancelledStatus)
|
||||
&& repoAccount.RepoStatusChanges.LastOrDefault(x => x.StatusTo == repoAccount.Status)?.ChangedDate <
|
||||
DateTime.Now.AddDays(-3).Date)
|
||||
{
|
||||
repoAccount.DateClosed = DateTime.Now;
|
||||
repoAccount.AccountLastProcessed = DateTime.Now;
|
||||
// TODO calling this still has a side effect.
|
||||
_eventAlertService.MarkAllAsRead(repoAccount.AccountNo).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
|
||||
public void TryUpdateCollateralRetitleDate(RepoAccount repo, TemenosCase temenosCase)
|
||||
{
|
||||
DateTime? retitledDate = (temenosCase.TitleOrdered) ? temenosCase.TitleOrderedDate : null;
|
||||
|
||||
foreach (Collateral collateral in repo.Collaterals)
|
||||
{
|
||||
collateral.RetitledInLandmarkName = retitledDate;
|
||||
}
|
||||
}
|
||||
|
||||
// This exists to update accounts not originally created from Temenos
|
||||
public void AddAccountUpdateRecord(RepoAccountProcessingSlip slip)
|
||||
{
|
||||
slip.ExistingRepoAccount.ReferenceId = slip.RecoveryAccount.AccountId;
|
||||
slip.ExistingRepoAccount.ReferenceCaseId = slip.RecoveryCase.CaseId;
|
||||
slip.ExistingRepoAccount.AccountLastProcessed = DateTime.Now;
|
||||
}
|
||||
|
||||
public void TryUpdateOrCreateRepoCompanyAssignment(RepoAccount repoAccount, TemenosCase temenosCase,
|
||||
Settings settings)
|
||||
{
|
||||
//Temenos may send blank string instead of null
|
||||
var repoCompanyIdFromCase = string.IsNullOrEmpty(temenosCase.RepoAgentId)
|
||||
? null
|
||||
: MapRepoAgentIdToRepoCompanyId(temenosCase.RepoAgentId, settings.RepoCompanies);
|
||||
|
||||
foreach (var collateral in repoAccount.Collaterals)
|
||||
{
|
||||
var mostRecent = collateral.RepoCompanySelections.OrderByDescending(x => x.DateTimeCreated)
|
||||
.FirstOrDefault();
|
||||
|
||||
// TODO, move to Slip, save elsewhere.
|
||||
var updated =
|
||||
UpdateOrCreateRepoCompanyAssignment(collateral, mostRecent, temenosCase, repoCompanyIdFromCase);
|
||||
|
||||
// This can no longer happen, but watch for it anyway.
|
||||
if (updated.Any(repoCompanySelection =>
|
||||
repoCompanySelection.DateRepoed != null &&
|
||||
repoCompanySelection.RepoCompanyAssignmentDate == null))
|
||||
{
|
||||
Log.Error(
|
||||
$"Tried to set DateRepoed before RepoCompanyAssignment for accountNo {repoAccount.AccountNo}. Please Investigate.");
|
||||
throw new MissingRequirementException(
|
||||
$"RepoCompanyAssignment for repo account {repoAccount.AccountNo} Missing RepoCompanyAssignmentDate",
|
||||
$"Cannot assign a DateRepoed before a RepoCompanyAssignment Date.");
|
||||
}
|
||||
|
||||
if (updated.Any())
|
||||
{
|
||||
_repoCompanySelectionService.SaveChangesAsync(updated).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<RepoCompanySelection> UpdateOrCreateRepoCompanyAssignment(Collateral collateral, RepoCompanySelection mostRecentSelection,
|
||||
TemenosCase temenosCase, int? repoCompanyIdFromCase)
|
||||
{
|
||||
var selectionUpdates = new List<RepoCompanySelection>();
|
||||
//If this is the first repoCompanySelection create when temenos has data
|
||||
if (mostRecentSelection == null)
|
||||
{
|
||||
if (temenosCase.RepoAgentId != null && temenosCase.RepoType != null &&
|
||||
temenosCase.DateRepoAssigned != null)
|
||||
{
|
||||
var newRepoCompanyEntry = CreateNewRepoCompanyAssignment(collateral, temenosCase, repoCompanyIdFromCase);
|
||||
collateral.RepoCompanySelections = collateral.RepoCompanySelections.Append(newRepoCompanyEntry).ToList();
|
||||
selectionUpdates.Add(newRepoCompanyEntry);
|
||||
}
|
||||
|
||||
return selectionUpdates;
|
||||
}
|
||||
|
||||
//If the repoCompany has changed update with temenos data
|
||||
if (mostRecentSelection.RepoCompanyId != repoCompanyIdFromCase ||
|
||||
mostRecentSelection.RepoType != temenosCase.RepoType ||
|
||||
mostRecentSelection.RepoCompanyAssignmentDate != temenosCase.DateRepoAssigned)
|
||||
{
|
||||
if (mostRecentSelection.DateRepoed == null)
|
||||
{
|
||||
mostRecentSelection.DateRepoed = DateTime.Now;
|
||||
if (mostRecentSelection.RepoCompanyAssignmentDate == null)
|
||||
{
|
||||
mostRecentSelection.RepoCompanyAssignmentDate = mostRecentSelection.DateTimeCreated ?? DateTime.Now;
|
||||
}
|
||||
|
||||
selectionUpdates.Add(mostRecentSelection);
|
||||
}
|
||||
|
||||
var newRepoCompanyEntry = CreateNewRepoCompanyAssignment(collateral, temenosCase, repoCompanyIdFromCase);
|
||||
collateral.RepoCompanySelections = collateral.RepoCompanySelections.Append(newRepoCompanyEntry).ToList();
|
||||
selectionUpdates.Add(newRepoCompanyEntry);
|
||||
|
||||
return selectionUpdates;
|
||||
}
|
||||
|
||||
//If no need to update or create, sync Repoed Date
|
||||
mostRecentSelection.DateRepoed = temenosCase.DateRepossessed;
|
||||
selectionUpdates.Add(mostRecentSelection);
|
||||
|
||||
return selectionUpdates;
|
||||
}
|
||||
|
||||
public RepoCompanySelection CreateNewRepoCompanyAssignment(Collateral collateral, TemenosCase temenosCase, int? newRepoCompanyId)
|
||||
{
|
||||
// DateRepoAssigned is defaulted to Today. The business (Bill) doesn't want it to throw, if they start having trouble
|
||||
// with the dates; change this to throw a MissingRequirementException on the field.
|
||||
var newRepoCompanySelection = new RepoCompanySelection()
|
||||
{
|
||||
RepoCompanyId = newRepoCompanyId,
|
||||
RepoType = temenosCase.RepoType ?? RepoType.Unknown,
|
||||
RepoCompanyAssignmentDate = temenosCase.DateRepoAssigned ?? DateTime.Now,
|
||||
DateRepoed = temenosCase.DateRepossessed,
|
||||
CollateralId = collateral.CollateralId,
|
||||
DateTimeCreated = DateTime.Now,
|
||||
Id = null
|
||||
};
|
||||
|
||||
return newRepoCompanySelection;
|
||||
}
|
||||
|
||||
public int? MapRepoAgentIdToRepoCompanyId(string repoAgentId, IReadOnlyCollection<RepoCompany> repoCompanies)
|
||||
{
|
||||
try
|
||||
{
|
||||
var repoCompany =
|
||||
repoCompanies.FirstOrDefault(company => company.ReferenceId == int.Parse(repoAgentId));
|
||||
|
||||
if (repoCompany != null)
|
||||
{
|
||||
return repoCompany.ID;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Error when trying to map RepoAgentID: {repoAgentId} to a RepoCompany ReferenceID", repoCompanies);
|
||||
}
|
||||
|
||||
// If you get this error, ensure there is a matching RepoCompany and that it has a RepoAgentID that matches the config call in Collections API.
|
||||
throw new MissingRequirementException("RepoCompanyAssignment - RepoCompanyID", $"Ensure a RepoCompany with RepoAgentID: {repoAgentId} is present");
|
||||
}
|
||||
|
||||
public IResult<DateTime?> GetDateTimeFromString(string dtString)
|
||||
{
|
||||
if (string.IsNullOrEmpty(dtString))
|
||||
{
|
||||
Log.Error("Cannot parse null or empty date string");
|
||||
return new DateTimeResult()
|
||||
{
|
||||
IsSuccess = false,
|
||||
ErrorMessage = "Cannot Parse Empty Date String",
|
||||
};
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return new DateTimeResult()
|
||||
{
|
||||
IsSuccess = true,
|
||||
Value = DateTime.Parse(dtString)
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Cannot Parse Date String {dtString}");
|
||||
return new DateTimeResult()
|
||||
{
|
||||
IsSuccess = false,
|
||||
ErrorMessage = $"There was an Exception when processing the date string: {dtString}"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public int? GetSixtyPlusUserNumber(TemenosAccount account)
|
||||
{
|
||||
try
|
||||
{
|
||||
account.CustomFieldMap.TryGetValue(SixtyPLusUserAssignedLabel, out string sixtyPlusUser);
|
||||
if (String.IsNullOrWhiteSpace(sixtyPlusUser))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Int32.TryParse(sixtyPlusUser, out int sixtyPlusUserNumber);
|
||||
|
||||
return sixtyPlusUserNumber;
|
||||
}
|
||||
catch(NullReferenceException nullEx)
|
||||
{
|
||||
Log.Warning($"60+ Collector key not found in Temenos Account for Account Number: {account.AccountNumber}. Error: {nullEx}");
|
||||
return null;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Log.Error($"Error while attempting to Parse 60+ Collector ID for Account Number: {account.AccountNumber}. Error: {ex}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Exceptions;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Repositories;
|
||||
using Serilog;
|
||||
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public class RefundStatusService : IRefundStatusService
|
||||
{
|
||||
private IRefundStatusRepository _refundStatusRepository;
|
||||
private IUnitOfWork _unitOfWork;
|
||||
private IRepoAccountService _repoAccountService;
|
||||
private IEmailer _emailer;
|
||||
|
||||
public RefundStatusService(
|
||||
IRefundStatusRepository refundStatusRepository,
|
||||
IUnitOfWork unitOfWork,
|
||||
IRepoAccountService repoAccountService,
|
||||
IEmailer emailer)
|
||||
{
|
||||
_refundStatusRepository = refundStatusRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
_repoAccountService = repoAccountService;
|
||||
_emailer = emailer;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<RefundStatus>> ListAllAsync()
|
||||
{
|
||||
return await _refundStatusRepository.ListAsync();
|
||||
}
|
||||
|
||||
public async Task<RefundStatus> GetAsync(int repoId)
|
||||
{
|
||||
RefundStatus refundStatus;
|
||||
try
|
||||
{
|
||||
refundStatus = await _refundStatusRepository.FindByRepoIdAsync(repoId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving Refund Status for repoId: {repoId}");
|
||||
throw new NotFoundException($"Refund Status with repoId {repoId} not found");
|
||||
}
|
||||
|
||||
if (refundStatus == null)
|
||||
{
|
||||
throw new NotFoundException($"Refund Status with repoId {repoId} not found");
|
||||
}
|
||||
|
||||
return refundStatus;
|
||||
}
|
||||
|
||||
public async Task<RefundStatus> SaveAsync(RefundStatus refundStatus)
|
||||
{
|
||||
await _repoAccountService.Get(refundStatus.RepoId.Value);
|
||||
|
||||
try
|
||||
{
|
||||
var newRefundStatus = await _refundStatusRepository.AddAsync(refundStatus);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return newRefundStatus;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure saving Refund Status: {refundStatus}", refundStatus);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RefundStatus> UpdateAsync(RefundStatus refundStatus)
|
||||
{
|
||||
var existingRefundStatus = await GetAsync(refundStatus.RepoId.Value);
|
||||
var existingRepoAccount = await _repoAccountService.Get(refundStatus.RepoId.Value);
|
||||
var isInBankruptcy = existingRepoAccount.Bankruptcy != null;
|
||||
var isEmailNeeded = refundStatus.RefundsReceivedDate != null && existingRefundStatus.RefundsReceivedDate != refundStatus.RefundsReceivedDate && !isInBankruptcy;
|
||||
|
||||
try
|
||||
{
|
||||
existingRefundStatus.WarrantyRefundStatus = refundStatus.WarrantyRefundStatus;
|
||||
existingRefundStatus.InsuranceRefundStatus = refundStatus.InsuranceRefundStatus;
|
||||
existingRefundStatus.PremiumDefiRefundStatus = refundStatus.PremiumDefiRefundStatus;
|
||||
existingRefundStatus.CPIRefundStatus = refundStatus.CPIRefundStatus;
|
||||
existingRefundStatus.RefundsReceivedDate = refundStatus.RefundsReceivedDate;
|
||||
|
||||
_refundStatusRepository.Update(existingRefundStatus);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
|
||||
if (isEmailNeeded)
|
||||
{
|
||||
await _emailer.SendSurplusDeficiencyLetterEmailAsync(existingRepoAccount);
|
||||
}
|
||||
|
||||
return existingRefundStatus;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure saving Refund Status: {refundStatus}", refundStatus);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+356
@@ -0,0 +1,356 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Exceptions;
|
||||
using LCU.API.Collections.RepoApi.Models;
|
||||
using LCU.API.Collections.RepoApi.Models.Filters;
|
||||
using LCU.API.Collections.RepoApi.Models.Requests;
|
||||
using LCU.Shared.Collections.RepoApiLib.Exceptions;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Producer;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Results;
|
||||
using LCU.Shared.Collections.RepoApiLib.Repositories;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public class RepoAccountService : IRepoAccountService
|
||||
{
|
||||
private readonly IRepoAccountRepository _repoAccountRepository;
|
||||
private readonly IRepoStatusChangeRepository _repoStatusChangeRepository;
|
||||
private readonly IRepoManualStatusRepository _repoManualStatusRepository;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
||||
public RepoAccountService(
|
||||
IRepoAccountRepository repoAccountRepository,
|
||||
IRepoStatusChangeRepository repoStatusChangeRepository,
|
||||
IRepoManualStatusRepository repoManualStatusRepository,
|
||||
IUnitOfWork unitOfWork)
|
||||
{
|
||||
_repoAccountRepository = repoAccountRepository;
|
||||
_repoStatusChangeRepository = repoStatusChangeRepository;
|
||||
_repoManualStatusRepository = repoManualStatusRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public async Task<PagedResult<RepoAccount>> ListAll(PageFilter pageFilter, RepoAccountPostFilter repoAccountFilter)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _repoAccountRepository.ListAsync(
|
||||
repoAccountFilter != null ? repoAccountFilter.RepoIdList : new List<int?>(),
|
||||
pageFilter.Page,
|
||||
pageFilter.Limit,
|
||||
repoAccountFilter?.Status,
|
||||
repoAccountFilter?.AuctionHouseId,
|
||||
repoAccountFilter?.RepoCompanyId,
|
||||
repoAccountFilter?.OnlyOpenAccounts);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving RepoAccounts");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RepoAccount> FindByAuctionAssignmentId(int auctionAssignmentId)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _repoAccountRepository.FindByAuctionAssignmentIdAsync(auctionAssignmentId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving RepoAccount");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RepoAccount> SaveAsync(RepoAccount repoAccount)
|
||||
{
|
||||
try
|
||||
{
|
||||
var newAccount = await _repoAccountRepository.AddAsync(repoAccount);
|
||||
RepoStatusChange change = new RepoStatusChange()
|
||||
{
|
||||
RepoId = newAccount.RepoId,
|
||||
ChangedBy = "System",
|
||||
StatusFrom = "None",
|
||||
StatusTo = repoAccount.Status,
|
||||
ChangedDate = DateTime.Now
|
||||
};
|
||||
await _repoStatusChangeRepository.AddAsync(change);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
|
||||
return repoAccount;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure saving Repo Account: {repoAccount}", repoAccount);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RepoAccount> Get(int id)
|
||||
{
|
||||
RepoAccount repoAccount;
|
||||
try
|
||||
{
|
||||
repoAccount = await _repoAccountRepository.FindByIdAsync(id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving Repo Account with id: {id}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
|
||||
if (repoAccount == null)
|
||||
{
|
||||
throw new NotFoundException($" Repo Accounts with id {id} not found.");
|
||||
}
|
||||
|
||||
return repoAccount;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<RepoAccount>> GetByAccountNo(string accountNo)
|
||||
{
|
||||
IReadOnlyCollection<RepoAccount> repoAccounts;
|
||||
try
|
||||
{
|
||||
repoAccounts = await _repoAccountRepository.FindByAccountNoAsync(accountNo);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving Repo Account by accountNo with accountNo: {accountNo}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
|
||||
if (repoAccounts == null || !repoAccounts.Any())
|
||||
{
|
||||
throw new NotFoundException($" Repo Accounts with id {accountNo} not found.");
|
||||
}
|
||||
|
||||
return repoAccounts;
|
||||
}
|
||||
|
||||
public async Task<RepoAccount> UpdateAsync(RepoAccount repoAccount, AppUser currentUser)
|
||||
{
|
||||
var existingRepoAccount = await _repoAccountRepository.FindByIdAsync(repoAccount.RepoId);
|
||||
|
||||
if (existingRepoAccount == null)
|
||||
{
|
||||
throw new NotFoundException($"Repo Account with repoId {repoAccount.RepoId} not found.");
|
||||
}
|
||||
|
||||
var originalStatus = existingRepoAccount.Status;
|
||||
|
||||
existingRepoAccount.AccountLastProcessed = repoAccount.AccountLastProcessed;
|
||||
existingRepoAccount.AccountNo = repoAccount.AccountNo;
|
||||
existingRepoAccount.AccountTypeCode = repoAccount.AccountTypeCode;
|
||||
existingRepoAccount.Balance = repoAccount.Balance;
|
||||
existingRepoAccount.Bankruptcy = repoAccount.Bankruptcy;
|
||||
existingRepoAccount.Borrowers = repoAccount.Borrowers;
|
||||
existingRepoAccount.Branch = repoAccount.Branch;
|
||||
// Only one field can change in collaterals when updating the account.
|
||||
if (existingRepoAccount.Collaterals != null)
|
||||
{
|
||||
foreach (var collateral in existingRepoAccount.Collaterals)
|
||||
{
|
||||
var newCollateral =
|
||||
repoAccount.Collaterals.FirstOrDefault(x => x.CollateralId == collateral.CollateralId);
|
||||
if (newCollateral != null)
|
||||
{
|
||||
collateral.RetitledInLandmarkName = newCollateral.RetitledInLandmarkName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// existingRepoAccount.Collaterals = repoAccount.Collaterals; This will fail, save separately.
|
||||
existingRepoAccount.Collector = repoAccount.Collector;
|
||||
existingRepoAccount.CurrentDelinquentAmount = repoAccount.CurrentDelinquentAmount;
|
||||
existingRepoAccount.DateClosed = repoAccount.DateClosed;
|
||||
existingRepoAccount.DateFirstDelinquent = repoAccount.DateFirstDelinquent;
|
||||
existingRepoAccount.DateFirstIntellect = repoAccount.DateFirstIntellect;
|
||||
existingRepoAccount.DaysDelinquent = repoAccount.DaysDelinquent;
|
||||
existingRepoAccount.DelinquentAmount = repoAccount.DelinquentAmount;
|
||||
existingRepoAccount.DueToPayDate = repoAccount.DueToPayDate;
|
||||
existingRepoAccount.LastPaymentDate = repoAccount.LastPaymentDate;
|
||||
existingRepoAccount.LoanNo = repoAccount.LoanNo;
|
||||
existingRepoAccount.LstPmtAmt = repoAccount.LstPmtAmt;
|
||||
existingRepoAccount.ManualRedemptionDate = repoAccount.ManualRedemptionDate;
|
||||
existingRepoAccount.MemberNo = repoAccount.MemberNo;
|
||||
existingRepoAccount.O4RDate = repoAccount.O4RDate;
|
||||
existingRepoAccount.OrigAmount = repoAccount.OrigAmount;
|
||||
existingRepoAccount.OrigAccountTypeCode = repoAccount.OrigAccountTypeCode;
|
||||
existingRepoAccount.PaidSkipAmount = repoAccount.PaidSkipAmount;
|
||||
existingRepoAccount.PrimaryIndividualId = repoAccount.PrimaryIndividualId;
|
||||
existingRepoAccount.RedemptionExpiredDate = repoAccount.RedemptionExpiredDate;
|
||||
existingRepoAccount.RefundStatus = repoAccount.RefundStatus;
|
||||
existingRepoAccount.RequiredPaymentAmount = repoAccount.RequiredPaymentAmount;
|
||||
existingRepoAccount.ReferenceId = repoAccount.ReferenceId;
|
||||
existingRepoAccount.ReferenceCaseId = repoAccount.ReferenceCaseId;
|
||||
existingRepoAccount.ReferenceStatus = repoAccount.ReferenceStatus;
|
||||
existingRepoAccount.Status = repoAccount.Status;
|
||||
existingRepoAccount.SubStatus = repoAccount.SubStatus;
|
||||
existingRepoAccount.WrittenOffDate = repoAccount.WrittenOffDate;
|
||||
existingRepoAccount.SixtyPlusCollectorId = repoAccount.SixtyPlusCollectorId;
|
||||
|
||||
try
|
||||
{
|
||||
_repoAccountRepository.Update(existingRepoAccount);
|
||||
if (originalStatus != repoAccount.Status)
|
||||
{
|
||||
var change = new RepoStatusChange()
|
||||
{
|
||||
RepoId = repoAccount.RepoId,
|
||||
ChangedBy = currentUser.FirstName + " " + currentUser.LastName,
|
||||
StatusFrom = originalStatus,
|
||||
StatusTo = repoAccount.Status,
|
||||
ChangedDate = DateTime.Now
|
||||
};
|
||||
await _repoStatusChangeRepository.AddAsync(change);
|
||||
}
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return await _repoAccountRepository.FindByIdAsync(repoAccount.RepoId);
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure updating Repo Account: {repoAccount}", repoAccount);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure updating Repo Account: {repoAccount}", repoAccount);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RepoAccount> DeleteAsync(int id)
|
||||
{
|
||||
RepoAccount existingRepoAccount;
|
||||
try
|
||||
{
|
||||
existingRepoAccount = await _repoAccountRepository.FindByIdAsync(id);
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Error finding an existing repo to delete with account ID: {id}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
|
||||
if (existingRepoAccount == null)
|
||||
{
|
||||
throw new NotFoundException($" Repo Account with id {id} not found.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_repoAccountRepository.Remove(existingRepoAccount);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return existingRepoAccount;
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure deleting Repo Account with id: {id}", id);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure deleting Repo Account with id: {id}", id);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<ProcessingItem>> GetItemsForProcessing()
|
||||
{
|
||||
try
|
||||
{
|
||||
var watchStatus = await _repoManualStatusRepository.ListWatchStatusesAsync();
|
||||
var repoAccountNos = await _repoAccountRepository.GetItemsForProcessing(watchStatus);
|
||||
return repoAccountNos;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure getting Repo Account Nos for polling");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<RepoAccount>> SearchRepoAccounts(string searchPhrase, int maxAllowedResults)
|
||||
{
|
||||
try
|
||||
{
|
||||
var matchedRepoAccounts = await _repoAccountRepository.SearchRepoAccounts(searchPhrase, maxAllowedResults);
|
||||
return matchedRepoAccounts;
|
||||
}
|
||||
catch (MaxItemException ex)
|
||||
{
|
||||
Log.Error(ex, $"Search Phrase Returned More Items Than Max (${maxAllowedResults}) searchPhrase:{searchPhrase}", searchPhrase);
|
||||
throw new MaxItemException(ex.ItemCount);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure Searching For Repo Accounts searchPhrase: {searchPhrase}", searchPhrase);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RepoAccount> GetNewestOpenAccount(string accountNo)
|
||||
{
|
||||
IReadOnlyCollection<RepoAccount> repoAccounts;
|
||||
try
|
||||
{
|
||||
repoAccounts = await _repoAccountRepository.FindByAccountNoAsync(accountNo);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving Repo Account by accountNo with accountNo: {accountNo}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
|
||||
if (repoAccounts == null || !repoAccounts.Any())
|
||||
{
|
||||
// You can't throw here, it's not an exception case, there simply isn't an account. (404)
|
||||
Log.Debug($" Repo Accounts with id {accountNo} not found.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return SelectFirstOpenAccount(repoAccounts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to select the repo account that should be updated. Since we have to go by AccountNo and there
|
||||
* may be more than one. Try to return an open account, and log if it detects more than one.
|
||||
*/
|
||||
public RepoAccount SelectFirstOpenAccount(IReadOnlyCollection<RepoAccount> repoAccounts)
|
||||
{
|
||||
if (repoAccounts.Count == 1)
|
||||
{
|
||||
return repoAccounts.First();
|
||||
}
|
||||
|
||||
RepoAccount openAccount = null;
|
||||
foreach (var repoAccount in repoAccounts.OrderBy(x => x.RepoId))
|
||||
{
|
||||
if (repoAccount.DateClosed == null)
|
||||
{
|
||||
if (openAccount != null)
|
||||
{
|
||||
Log.Fatal($"Multiple -OPEN- Repos with AccountNo: {openAccount.AccountNo}, {openAccount.RepoId} / {repoAccount.RepoId}");
|
||||
return openAccount;
|
||||
}
|
||||
openAccount = repoAccount;
|
||||
}
|
||||
}
|
||||
|
||||
if (openAccount != null)
|
||||
{
|
||||
return openAccount;
|
||||
}
|
||||
|
||||
return repoAccounts.OrderByDescending(x => x.DateClosed).ToList().FirstOrDefault();
|
||||
}
|
||||
}
|
||||
}
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Exceptions;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Repositories;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public class RepoCompanySelectionService : IRepoCompanySelectionService
|
||||
{
|
||||
private readonly IRepoCompanySelectionRepository _repoCompanySelectionRepository;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
||||
public RepoCompanySelectionService(
|
||||
IRepoCompanySelectionRepository repoCompanySelectionRepository,
|
||||
IUnitOfWork unitOfWork)
|
||||
{
|
||||
_repoCompanySelectionRepository = repoCompanySelectionRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<RepoCompanySelection>> ListAllAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _repoCompanySelectionRepository.ListAllAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving Repo Companies");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RepoCompanySelection> GetAsync(int id)
|
||||
{
|
||||
RepoCompanySelection repoCompanySelection;
|
||||
try
|
||||
{
|
||||
repoCompanySelection = await _repoCompanySelectionRepository.FindByIdAsync(id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving Repo Company Selection with id: {id}");
|
||||
throw new NotFoundException($"Repo Company Selection with id {id} not found");
|
||||
}
|
||||
|
||||
if (repoCompanySelection == null)
|
||||
{
|
||||
throw new NotFoundException($"Repo Company Selection with id {id} not found");
|
||||
}
|
||||
|
||||
return repoCompanySelection;
|
||||
}
|
||||
|
||||
public async Task SaveChangesAsync(IList<RepoCompanySelection> repoCompanySelections)
|
||||
{
|
||||
_repoCompanySelectionRepository.SaveRange(repoCompanySelections);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+158
@@ -0,0 +1,158 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlClient;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Exceptions;
|
||||
using LCU.API.Collections.RepoApi.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Repositories;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public class RepoCompanyService : IRepoCompanyService
|
||||
{
|
||||
private IRepoCompanyRepository _repoCompanyRepository;
|
||||
private IUnitOfWork _unitOfWork;
|
||||
|
||||
public RepoCompanyService(
|
||||
IRepoCompanyRepository repoCompanyRepository,
|
||||
IUnitOfWork unitOfWork)
|
||||
{
|
||||
_repoCompanyRepository = repoCompanyRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<RepoCompany>> ListAll()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _repoCompanyRepository.ListAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving Repo Companies");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RepoCompany> Get(int id)
|
||||
{
|
||||
RepoCompany repoCompany;
|
||||
try
|
||||
{
|
||||
repoCompany = await _repoCompanyRepository.FindByIdAsync(id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving Repo Company with id: {id}");
|
||||
throw new NotFoundException($"Repo Company with id {id} not found");
|
||||
}
|
||||
|
||||
if (repoCompany == null)
|
||||
{
|
||||
throw new NotFoundException($"Repo Company with id {id} not found");
|
||||
}
|
||||
|
||||
return repoCompany;
|
||||
}
|
||||
|
||||
|
||||
public async Task<RepoCompany> Update(RepoCompany repoCompany, AppUser user)
|
||||
{
|
||||
var existing = await _repoCompanyRepository.FindByIdAsync(repoCompany.ID);
|
||||
|
||||
if (existing == null)
|
||||
{
|
||||
throw new NotFoundException($"Repo Company with id {repoCompany.ID} not found");
|
||||
}
|
||||
|
||||
existing.Name = repoCompany.Name;
|
||||
existing.Email = repoCompany.Email;
|
||||
existing.Address_Line_1 = repoCompany.Address_Line_1;
|
||||
existing.Address_Line_2 = repoCompany.Address_Line_2;
|
||||
existing.City = repoCompany.City;
|
||||
existing.State = repoCompany.State;
|
||||
existing.ZIP = repoCompany.ZIP;
|
||||
existing.Country = repoCompany.Country;
|
||||
existing.Phone = repoCompany.Phone;
|
||||
existing.Fax = repoCompany.Fax;
|
||||
existing.ReferenceId = repoCompany.ReferenceId;
|
||||
|
||||
if (existing.DisabledDate != repoCompany.DisabledDate)
|
||||
{
|
||||
if (repoCompany.DisabledDate == null)
|
||||
{
|
||||
existing.DisabledDate = null;
|
||||
existing.DisabledBy = null;
|
||||
} else
|
||||
{
|
||||
existing.DisabledDate = repoCompany.DisabledDate;
|
||||
existing.DisabledBy = user.FirstName + " " + user.LastName;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_repoCompanyRepository.Update(existing);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return existing;
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Filure updating Repo Company: {repoCompany}", repoCompany);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure updating Repo Repo Company: {repoCompany}", repoCompany);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RepoCompany> Save(RepoCompany repoCompany)
|
||||
{
|
||||
try
|
||||
{
|
||||
var newCompany = await _repoCompanyRepository.AddAsync(repoCompany);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return newCompany;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure saving Repo Company: {repoCompany}", repoCompany);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async Task<RepoCompany> Delete(int id)
|
||||
{
|
||||
var existingRepoCompany = await _repoCompanyRepository.FindByIdAsync(id);
|
||||
|
||||
if (existingRepoCompany == null)
|
||||
{
|
||||
throw new NotFoundException($" Repo Company with id {id} not found.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_repoCompanyRepository.Remove(existingRepoCompany);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return existingRepoCompany;
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure deleting Repo Company with id: {id}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure deleting Repo Company with id: {id}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+92
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.CollectionsApiLib.Models;
|
||||
using LCU.API.Collections.RepoApi.DataAccess;
|
||||
using LCU.API.Collections.RepoApi.Exceptions;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Repositories;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public class RepoLetterService : IRepoLetterService
|
||||
{
|
||||
private readonly ILetterRepository _letterRepository;
|
||||
private readonly ICollectionsDao _collectionsDao;
|
||||
|
||||
public RepoLetterService(ILetterRepository letterRepository,
|
||||
ICollectionsDao collectionsDao)
|
||||
{
|
||||
_letterRepository = letterRepository;
|
||||
_collectionsDao = collectionsDao;
|
||||
}
|
||||
|
||||
public async Task<IReadOnlyCollection<RepoLetter>> ListByAccountIdAsync(int accountId)
|
||||
{
|
||||
try
|
||||
{
|
||||
var collectionsHistory = await _collectionsDao.RetrieveHistoryAsync(accountId);
|
||||
var collectionsLetters =
|
||||
collectionsHistory.Where(history => history.Type == HistoryType.Letter).ToList();
|
||||
|
||||
if (!collectionsLetters.Any())
|
||||
{
|
||||
return new List<RepoLetter>();
|
||||
}
|
||||
|
||||
var letters = await _letterRepository.ListAsync();
|
||||
return ProcessLetters(collectionsLetters, letters.ToList());
|
||||
}
|
||||
catch (MissingRequirementException ex)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving Letter List");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public static IReadOnlyCollection<RepoLetter> ProcessLetters(List<TemenosHistory> collectionsLetters,
|
||||
List<Letter> letters)
|
||||
{
|
||||
// GJF: Are you here to fix a bug? Probably. These don't match up, who wrote this?!?! This code is fueled by hope.
|
||||
// Ok, so you really need to fix it. Go out to Temenos, go to System Management (the button is labelled 'tools'!)
|
||||
// Find the Communication section, expand it and choose 'letters & forms'. Now find the right Letter and
|
||||
// update the LetterDescription column in the Letters table of RepoDb for the correct letter.
|
||||
var resultList = new List<RepoLetter>();
|
||||
foreach (var letter in letters)
|
||||
{
|
||||
foreach (var collectionsLetter in collectionsLetters)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(letter.LetterDescription))
|
||||
{
|
||||
// If this exception is happening, see LetterUpdate.sql. Also See Above.
|
||||
throw new MissingRequirementException("LetterDescription", "Letter table requires LetterDescription Column entry.");
|
||||
}
|
||||
|
||||
if (collectionsLetter.Message.Trim().ToUpper()
|
||||
.Contains(letter.LetterDescription.Trim().ToUpper()))
|
||||
{
|
||||
resultList.Add(new RepoLetter
|
||||
{
|
||||
NoticeType = collectionsLetter.Message,
|
||||
DateSent = collectionsLetter.Created,
|
||||
DateExpired = collectionsLetter.Created.AddDays(letter.DaysToWait ?? 0),
|
||||
DaysToRedeem = letter.DaysToWait ?? 0,
|
||||
ActionCode = letter.ActionCode.Trim()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return resultList
|
||||
.GroupBy(x => x.NoticeType.Trim(),
|
||||
(key, g) => g.OrderByDescending(x => x.DateSent).FirstOrDefault())
|
||||
.OrderByDescending(x => x.DateSent).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Exceptions;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Repositories;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public class RepoStatusChangeService : IRepoStatusChangeService
|
||||
{
|
||||
private readonly IRepoStatusChangeRepository _repoStatusChangeRepository;
|
||||
|
||||
public RepoStatusChangeService(
|
||||
IRepoStatusChangeRepository repoStatusChangeRepository)
|
||||
{
|
||||
_repoStatusChangeRepository = repoStatusChangeRepository;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<RepoStatusChange>> ListByAccount(int repoId)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _repoStatusChangeRepository.ListForAccountAsync(repoId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving Repo Account Status Change List");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+282
@@ -0,0 +1,282 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Exceptions;
|
||||
using LCU.API.Collections.RepoApi.Factories;
|
||||
using LCU.Shared.Collections.RepoApiLib.Formatting;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Reporting.Responses;
|
||||
using LCU.Shared.Collections.RepoApiLib.Repositories;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.Reporting
|
||||
{
|
||||
public class AuctionReportingService : IAuctionReportingService
|
||||
{
|
||||
private readonly IReportingRepository _reportingRepository;
|
||||
|
||||
public AuctionReportingService(IReportingRepository reportingRepository)
|
||||
{
|
||||
_reportingRepository = reportingRepository;
|
||||
}
|
||||
|
||||
public async Task<ReportTableResponse> GetAssignedBalance(DateTime lowDateTime, DateTime highDateTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reportData = await _reportingRepository.GetAccountsByDateRepoed(lowDateTime, highDateTime);
|
||||
|
||||
return new ReportTableResponse()
|
||||
{
|
||||
ReportTable = ReportTableFactory.CreateHistoricalSummationTable(
|
||||
ReportSumType.Currency,
|
||||
reportData,
|
||||
"Auctions",
|
||||
lowDateTime,
|
||||
highDateTime,
|
||||
(reportEntry) => reportEntry.Balance,
|
||||
(reportEntry) => reportEntry.AuctionName,
|
||||
(reportEntry) => reportEntry.DateRepoed,
|
||||
(reportEntry) => reportEntry.RepoId),
|
||||
DataTable = DataTableFormatting.CreateDataTable(
|
||||
reportData,
|
||||
(entry) => entry.RepoId,
|
||||
StdReportEntryColumns.AccountNo(),
|
||||
StdReportEntryColumns.Balance(),
|
||||
StdReportEntryColumns.Auction(),
|
||||
StdReportEntryColumns.Collateral(),
|
||||
StdReportEntryColumns.RepoCompany(),
|
||||
StdReportEntryColumns.DateRepoed(),
|
||||
StdReportEntryColumns.DateEnteredAutoIMS(),
|
||||
StdReportEntryColumns.ScheduledSaleDate())
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving Gross Active Balance Report");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ReportTableResponse> GetAssignedCollateralCount(DateTime lowDateTime, DateTime highDateTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reportData = await _reportingRepository.GetAccountsByDateRepoed(lowDateTime, highDateTime);
|
||||
|
||||
return new ReportTableResponse()
|
||||
{
|
||||
ReportTable = ReportTableFactory.CreateHistoricalSummationTable(
|
||||
ReportSumType.WholeNumber,
|
||||
reportData,
|
||||
"Auctions",
|
||||
lowDateTime,
|
||||
highDateTime,
|
||||
(reportEntry) => 1,
|
||||
(reportEntry) => reportEntry.AuctionName,
|
||||
(reportEntry) => reportEntry.DateRepoed,
|
||||
(reportEntry) => reportEntry.RepoId),
|
||||
DataTable = DataTableFormatting.CreateDataTable(
|
||||
reportData,
|
||||
(entry) => entry.RepoId,
|
||||
StdReportEntryColumns.Member(),
|
||||
StdReportEntryColumns.AccountNo(),
|
||||
StdReportEntryColumns.Status(),
|
||||
StdReportEntryColumns.DaysInStatus(),
|
||||
StdReportEntryColumns.Balance(),
|
||||
StdReportEntryColumns.AccountCode(),
|
||||
StdReportEntryColumns.Bankruptcy(),
|
||||
StdReportEntryColumns.Collateral(),
|
||||
StdReportEntryColumns.DateRepoed(),
|
||||
StdReportEntryColumns.Grade(),
|
||||
StdReportEntryColumns.FloorPrice(),
|
||||
StdReportEntryColumns.Auction())
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving Active Inventory Report");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ReportTableResponse> GetSalePriceReport(DateTime lowDateTime, DateTime highDateTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reportData = await _reportingRepository.GetAccountsByDateSoldInAutoIMS(lowDateTime, highDateTime);
|
||||
|
||||
return new ReportTableResponse()
|
||||
{
|
||||
ReportTable = ReportTableFactory.CreateHistoricalSummationTable(
|
||||
ReportSumType.Currency,
|
||||
reportData,
|
||||
"Auctions",
|
||||
lowDateTime,
|
||||
highDateTime,
|
||||
(reportEntry) => reportEntry.GrossSalePrice,
|
||||
(reportEntry) => reportEntry.AuctionName,
|
||||
(reportEntry) => reportEntry.SoldDate,
|
||||
(reportEntry) => reportEntry.RepoId,
|
||||
false),
|
||||
DataTable = DataTableFormatting.CreateDataTable(
|
||||
reportData,
|
||||
(entry) => entry.RepoId,
|
||||
StdReportEntryColumns.Member(),
|
||||
StdReportEntryColumns.AccountNo(),
|
||||
StdReportEntryColumns.Status(),
|
||||
StdReportEntryColumns.DaysInStatus(),
|
||||
StdReportEntryColumns.Balance(),
|
||||
StdReportEntryColumns.AccountCode(),
|
||||
StdReportEntryColumns.Bankruptcy(),
|
||||
StdReportEntryColumns.Collateral(),
|
||||
StdReportEntryColumns.Grade(),
|
||||
StdReportEntryColumns.FloorPrice(),
|
||||
StdReportEntryColumns.Auction(),
|
||||
StdReportEntryColumns.DateSold(),
|
||||
StdReportEntryColumns.GrossSalePrice(),
|
||||
StdReportEntryColumns.GrossPercentFloor(),
|
||||
StdReportEntryColumns.NetPercentFloor(),
|
||||
StdReportEntryColumns.TotalCharges())
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failture Retrieving Sold Price Report.");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ReportTableResponse> GetAuctionFeeReport(DateTime lowDateTime, DateTime highDateTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reportData = await _reportingRepository.GetAccountsByDateSoldInAutoIMS(lowDateTime, highDateTime);
|
||||
|
||||
return new ReportTableResponse()
|
||||
{
|
||||
ReportTable = ReportTableFactory.CreateHistoricalSummationTable(
|
||||
ReportSumType.Currency,
|
||||
reportData,
|
||||
"Auctions",
|
||||
lowDateTime,
|
||||
highDateTime,
|
||||
(reportEntry) => reportEntry.TotalCharges,
|
||||
(reportEntry) => reportEntry.AuctionName,
|
||||
(reportEntry) => reportEntry.SoldDate,
|
||||
(reportEntry) => reportEntry.RepoId,
|
||||
false),
|
||||
DataTable = DataTableFormatting.CreateDataTable(
|
||||
reportData,
|
||||
(entry) => entry.RepoId,
|
||||
StdReportEntryColumns.Member(),
|
||||
StdReportEntryColumns.AccountNo(),
|
||||
StdReportEntryColumns.Status(),
|
||||
StdReportEntryColumns.DaysInStatus(),
|
||||
StdReportEntryColumns.Balance(),
|
||||
StdReportEntryColumns.AccountCode(),
|
||||
StdReportEntryColumns.Bankruptcy(),
|
||||
StdReportEntryColumns.Collateral(),
|
||||
StdReportEntryColumns.Auction(),
|
||||
StdReportEntryColumns.DateSold(),
|
||||
StdReportEntryColumns.TotalCharges())
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failture Retrieving Auction Fee Report.");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ReportTableResponse> GetAvgDaysAssignedToSold(DateTime lowDateTime, DateTime highDateTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reportData = await _reportingRepository.GetAccountsByDateSoldInAutoIMS(lowDateTime, highDateTime);
|
||||
|
||||
return new ReportTableResponse()
|
||||
{
|
||||
ReportTable = ReportTableFactory.CreateHistoricalSummationTable(
|
||||
ReportSumType.WholeNumberAvg,
|
||||
reportData,
|
||||
"Auctions",
|
||||
lowDateTime,
|
||||
highDateTime,
|
||||
(reportEntry) =>
|
||||
{
|
||||
if (reportEntry.SoldDate != null && reportEntry.DateEnteredAutoIMS != null)
|
||||
{
|
||||
return (decimal) (reportEntry.SoldDate.Value - reportEntry.DateEnteredAutoIMS.Value).TotalDays;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
(reportEntry) => reportEntry.AuctionName,
|
||||
(reportEntry) => reportEntry.SoldDate,
|
||||
(reportEntry) => reportEntry.RepoId,
|
||||
false),
|
||||
DataTable = DataTableFormatting.CreateDataTable(
|
||||
reportData,
|
||||
(entry) => entry.RepoId,
|
||||
StdReportEntryColumns.Member(),
|
||||
StdReportEntryColumns.AccountNo(),
|
||||
StdReportEntryColumns.Status(),
|
||||
StdReportEntryColumns.DaysInStatus(),
|
||||
StdReportEntryColumns.Balance(),
|
||||
StdReportEntryColumns.AccountCode(),
|
||||
StdReportEntryColumns.Bankruptcy(),
|
||||
StdReportEntryColumns.Collateral(),
|
||||
StdReportEntryColumns.Auction(),
|
||||
StdReportEntryColumns.DateEnteredAutoIMS("Date Assigned"),
|
||||
StdReportEntryColumns.DateSold(),
|
||||
StdReportEntryColumns.AutoIMSAssignmentToSold("Days Assigned to Sold"))
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failture Retrieving Avg. Days Assigned to Sold Report.");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ReportTableResponse> GetAvgNetOfFloor(DateTime lowDateTime, DateTime highDateTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reportData = await _reportingRepository.GetAccountsByDateSoldInAutoIMS(lowDateTime, highDateTime);
|
||||
|
||||
return new ReportTableResponse()
|
||||
{
|
||||
ReportTable = ReportTableFactory.CreateHistoricalSummationTable(
|
||||
ReportSumType.PercentageAvg,
|
||||
reportData,
|
||||
"Auctions",
|
||||
lowDateTime,
|
||||
highDateTime,
|
||||
(reportEntry) => reportEntry.NetPercentFloor,
|
||||
(reportEntry) => reportEntry.AuctionName,
|
||||
(reportEntry) => reportEntry.SoldDate,
|
||||
(reportEntry) => reportEntry.RepoId,
|
||||
false),
|
||||
DataTable = DataTableFormatting.CreateDataTable(
|
||||
reportData,
|
||||
(entry) => entry.RepoId,
|
||||
StdReportEntryColumns.Member(),
|
||||
StdReportEntryColumns.AccountNo(),
|
||||
StdReportEntryColumns.Status(),
|
||||
StdReportEntryColumns.DaysInStatus(),
|
||||
StdReportEntryColumns.Balance(),
|
||||
StdReportEntryColumns.AccountCode(),
|
||||
StdReportEntryColumns.Bankruptcy(),
|
||||
StdReportEntryColumns.Collateral(),
|
||||
StdReportEntryColumns.Auction(),
|
||||
StdReportEntryColumns.NetPercentFloor(),
|
||||
StdReportEntryColumns.DateSold())
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failture Retrieving Avg. Days Assigned to Sold Report.");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Reporting.Responses;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.Reporting
|
||||
{
|
||||
public interface IAuctionReportingService
|
||||
{
|
||||
Task<ReportTableResponse> GetAssignedBalance(DateTime lowDateTime, DateTime highDateTime);
|
||||
Task<ReportTableResponse> GetAssignedCollateralCount(DateTime lowDateTime, DateTime highDateTime);
|
||||
Task<ReportTableResponse> GetSalePriceReport(DateTime lowDateTime, DateTime highDateTime);
|
||||
Task<ReportTableResponse> GetAuctionFeeReport(DateTime lowDateTime, DateTime highDateTime);
|
||||
Task<ReportTableResponse> GetAvgDaysAssignedToSold(DateTime lowDateTime, DateTime highDateTime);
|
||||
Task<ReportTableResponse> GetAvgNetOfFloor(DateTime lowDateTime, DateTime highDateTime);
|
||||
}
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Reporting.Responses;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.Reporting
|
||||
{
|
||||
public interface ILandmarkReportingService
|
||||
{
|
||||
Task<ReportTableResponse> GetIsActiveAccountReport(DateTime reportingRequestLowDate, DateTime reportingRequestHighDate);
|
||||
Task<ReportTableResponse> GetIsClosedAccountReport(DateTime lowDateTime, DateTime highDateTime);
|
||||
Task<ReportTableResponse> GetAvgDaysRepoToSoldReport(DateTime lowDateTime, DateTime highDateTime);
|
||||
Task<ReportTableResponse> GetRedemptionCountReport(DateTime lowDateTime, DateTime highDateTime);
|
||||
Task<ReportTableResponse> GetAvgDaysDelqWhenAddedReport(DateTime lowDateTime, DateTime highDateTime);
|
||||
Task<ReportTableResponse> GetCountOfSalesReport(DateTime lowDateTime, DateTime highDateTime);
|
||||
Task<ReportTableResponse> GetAvgSurplusReport(DateTime lowDateTime, DateTime highDateTime);
|
||||
Task<ReportTableResponse> GetRepoBalanceReport(DateTime lowDateTime, DateTime highDateTime);
|
||||
Task<ReportTableResponse> GetRedemptionBalanceReport(DateTime lowDateTime, DateTime highDateTime);
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Reporting.Responses;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.Reporting
|
||||
{
|
||||
public interface IRepoCompanyReportingService
|
||||
{
|
||||
Task<ReportTableResponse> GetAssignmentCountReport(DateTime lowDateTime, DateTime highDateTime);
|
||||
Task<ReportTableResponse> GetRepoedCountReport(DateTime lowDateTime, DateTime highDateTime);
|
||||
Task<ReportTableResponse> GetAvgDaysAssignedToRepoed(DateTime lowDateTime, DateTime highDateTime);
|
||||
Task<ReportTableResponse> GetAssignedBalance(DateTime lowDateTime, DateTime highDateTime);
|
||||
}
|
||||
}
|
||||
+474
@@ -0,0 +1,474 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Exceptions;
|
||||
using LCU.API.Collections.RepoApi.Factories;
|
||||
using LCU.Shared.Collections.RepoApiLib.Formatting;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Reporting.Responses;
|
||||
using LCU.Shared.Collections.RepoApiLib.Repositories;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.Reporting
|
||||
{
|
||||
public class LandmarkReportingService : ILandmarkReportingService
|
||||
{
|
||||
private readonly IReportingRepository _reportingRepository;
|
||||
private const string RedeemedStatus = "REDEEMED";
|
||||
|
||||
public LandmarkReportingService(IReportingRepository reportingRepository)
|
||||
{
|
||||
_reportingRepository = reportingRepository;
|
||||
}
|
||||
|
||||
public async Task<ReportTableResponse> GetIsActiveAccountReport(DateTime lowDateTime, DateTime highDateTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reportData = await _reportingRepository.GetActiveAccounts();
|
||||
|
||||
return new ReportTableResponse()
|
||||
{
|
||||
DataTable = new DataTable()
|
||||
{
|
||||
Headers = new string[]
|
||||
{
|
||||
"Member Name", "Account No", "Status", "Days In Status", "Days Delinquent", "Last Payment Amount", "Last Payment Date",
|
||||
"Delinquent Amount", "Repo Type", "Balance", "Account Code", "Bankruptcy", "Collector", "Year", "Make", "Model",
|
||||
"Grade", "Floor Price", "Auction House", "Date Entered AutoIMS","Sold Date", "Gross % Floor", "Net % Floor", "Gross Sale Price",
|
||||
"Total Charges", "Repo Company", "Date Assigned", "Date Repoed", "Scheduled Sale Date"
|
||||
},
|
||||
Rows = reportData.Select(reportRow => new RepoAccountReferenceRow()
|
||||
{
|
||||
RepoId = reportRow.RepoId,
|
||||
Cells = new ReportCell[]
|
||||
{
|
||||
ReportCellFormatting.CreateStringCell(reportRow.IndividualName),
|
||||
ReportCellFormatting.CreateStringCell(reportRow.AccountNo),
|
||||
ReportCellFormatting.CreateStringCell(reportRow.Status),
|
||||
ReportCellFormatting.CreateIntCell(reportRow.DaysInStatus),
|
||||
ReportCellFormatting.CreateIntCell(reportRow.DaysDelinquent),
|
||||
ReportCellFormatting.CreateCurrencyCell(reportRow.LastPaymentAmount),
|
||||
ReportCellFormatting.CreateDateCell(reportRow.LastPaymentDate),
|
||||
ReportCellFormatting.CreateCurrencyCell(reportRow.DelinquentAmount),
|
||||
ReportCellFormatting.CreateStringCell(reportRow.RepoType),
|
||||
ReportCellFormatting.CreateCurrencyCell(reportRow.Balance),
|
||||
ReportCellFormatting.CreateStringCell(reportRow.AccountTypeCode),
|
||||
ReportCellFormatting.CreateStringCell(reportRow.Bankruptcy),
|
||||
ReportCellFormatting.CreateStringCell(reportRow.Collector),
|
||||
ReportCellFormatting.CreateIntCell(reportRow.Year),
|
||||
ReportCellFormatting.CreateStringCell(reportRow.Make),
|
||||
ReportCellFormatting.CreateStringCell(reportRow.Model),
|
||||
ReportCellFormatting.CreateDecimalCell(reportRow.Grade),
|
||||
ReportCellFormatting.CreateCurrencyCell(reportRow.FloorPrice),
|
||||
ReportCellFormatting.CreateStringCell(reportRow.AuctionHouse),
|
||||
ReportCellFormatting.CreateDateCell(reportRow.DateEnteredAutoIMS),
|
||||
ReportCellFormatting.CreateDateCell(reportRow.SoldDate),
|
||||
ReportCellFormatting.CreatePercentageCell(reportRow.GrossPercentFloor),
|
||||
ReportCellFormatting.CreatePercentageCell(reportRow.NetPercentFloor),
|
||||
ReportCellFormatting.CreateCurrencyCell(reportRow.GrossSalePrice),
|
||||
ReportCellFormatting.CreateCurrencyCell(reportRow.TotalCharges),
|
||||
ReportCellFormatting.CreateStringCell(reportRow.RepoCompany),
|
||||
ReportCellFormatting.CreateDateCell(reportRow.DateAssigned),
|
||||
ReportCellFormatting.CreateDateCell(reportRow.DateRepoed),
|
||||
ReportCellFormatting.CreateDateCell(reportRow.ScheduledSaleDate)
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving Is Active Account Report");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ReportTableResponse> GetIsClosedAccountReport(DateTime lowDateTime, DateTime highDateTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reportData = await _reportingRepository.GetClosedAccounts(lowDateTime, highDateTime);
|
||||
|
||||
return new ReportTableResponse()
|
||||
{
|
||||
DataTable = new DataTable()
|
||||
{
|
||||
Headers = new string[]
|
||||
{
|
||||
"Member Name", "Account No", "Date Closed", "Status", "Days In Status", "Days Delinquent", "Last Payment Amount", "Last Payment Date",
|
||||
"Delinquent Amount", "Repo Type", "Balance", "Account Code", "Bankruptcy", "Collector", "Year", "Make", "Model",
|
||||
"Grade", "Floor Price", "Auction House", "Date Entered AutoIMS","Sold Date", "Gross % Floor", "Net % Floor", "Gross Sale Price",
|
||||
"Total Charges", "Repo Company", "Date Assigned", "Date Repoed", "Scheduled Sale Date"
|
||||
},
|
||||
Rows = reportData.Select(reportRow => new RepoAccountReferenceRow()
|
||||
{
|
||||
RepoId = reportRow.RepoId,
|
||||
Cells = new ReportCell[]
|
||||
{
|
||||
ReportCellFormatting.CreateStringCell(reportRow.IndividualName),
|
||||
ReportCellFormatting.CreateStringCell(reportRow.AccountNo),
|
||||
ReportCellFormatting.CreateDateCell(reportRow.DateClosed),
|
||||
ReportCellFormatting.CreateStringCell(reportRow.Status),
|
||||
ReportCellFormatting.CreateIntCell(reportRow.DaysInStatus),
|
||||
ReportCellFormatting.CreateIntCell(reportRow.DaysDelinquent),
|
||||
ReportCellFormatting.CreateCurrencyCell(reportRow.LastPaymentAmount),
|
||||
ReportCellFormatting.CreateDateCell(reportRow.LastPaymentDate),
|
||||
ReportCellFormatting.CreateCurrencyCell(reportRow.DelinquentAmount),
|
||||
ReportCellFormatting.CreateStringCell(reportRow.RepoType),
|
||||
ReportCellFormatting.CreateCurrencyCell(reportRow.Balance),
|
||||
ReportCellFormatting.CreateStringCell(reportRow.AccountTypeCode),
|
||||
ReportCellFormatting.CreateStringCell(reportRow.Bankruptcy),
|
||||
ReportCellFormatting.CreateStringCell(reportRow.Collector),
|
||||
ReportCellFormatting.CreateIntCell(reportRow.Year),
|
||||
ReportCellFormatting.CreateStringCell(reportRow.Make),
|
||||
ReportCellFormatting.CreateStringCell(reportRow.Model),
|
||||
ReportCellFormatting.CreateDecimalCell(reportRow.Grade),
|
||||
ReportCellFormatting.CreateCurrencyCell(reportRow.FloorPrice),
|
||||
ReportCellFormatting.CreateStringCell(reportRow.AuctionHouse),
|
||||
ReportCellFormatting.CreateDateCell(reportRow.DateEnteredAutoIMS),
|
||||
ReportCellFormatting.CreateDateCell(reportRow.SoldDate),
|
||||
ReportCellFormatting.CreatePercentageCell(reportRow.GrossPercentFloor),
|
||||
ReportCellFormatting.CreatePercentageCell(reportRow.NetPercentFloor),
|
||||
ReportCellFormatting.CreateCurrencyCell(reportRow.GrossSalePrice),
|
||||
ReportCellFormatting.CreateCurrencyCell(reportRow.TotalCharges),
|
||||
ReportCellFormatting.CreateStringCell(reportRow.RepoCompany),
|
||||
ReportCellFormatting.CreateDateCell(reportRow.DateAssigned),
|
||||
ReportCellFormatting.CreateDateCell(reportRow.DateRepoed),
|
||||
ReportCellFormatting.CreateDateCell(reportRow.ScheduledSaleDate)
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving Is Closed Account Report");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ReportTableResponse> GetAvgDaysRepoToSoldReport(DateTime lowDateTime, DateTime highDateTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reportData = await _reportingRepository.GetAccountsByStatusChangeInPeriod("SOLD", lowDateTime, highDateTime);
|
||||
|
||||
return new ReportTableResponse()
|
||||
{
|
||||
ReportTable = ReportTableFactory.CreateHistoricalSummationTable(
|
||||
ReportSumType.WholeNumberAvg,
|
||||
reportData,
|
||||
"Sold Accounts",
|
||||
lowDateTime,
|
||||
highDateTime,
|
||||
(reportEntry) =>
|
||||
{
|
||||
if (reportEntry.FilteredStatusChangeDate != null && reportEntry.DateRepoed != null)
|
||||
{
|
||||
return (decimal)(reportEntry.FilteredStatusChangeDate.Value - reportEntry.DateRepoed.Value).TotalDays;
|
||||
}
|
||||
return null;
|
||||
},
|
||||
(reportEntry) => null,
|
||||
(reportEntry) => reportEntry.FilteredStatusChangeDate,
|
||||
(reportEntry) => reportEntry.RepoId,
|
||||
false),
|
||||
DataTable = DataTableFormatting.CreateDataTable(
|
||||
reportData,
|
||||
(entry) => entry.RepoId,
|
||||
StdReportEntryColumns.Member(),
|
||||
StdReportEntryColumns.AccountNo(),
|
||||
StdReportEntryColumns.Status(),
|
||||
StdReportEntryColumns.DaysCurrentDelq(),
|
||||
StdReportEntryColumns.DaysDelqWhenAdded(),
|
||||
StdReportEntryColumns.LastPaymentAmount(),
|
||||
StdReportEntryColumns.LastPaymentDate(),
|
||||
StdReportEntryColumns.DelinquentAmount(),
|
||||
StdReportEntryColumns.RepoType(),
|
||||
StdReportEntryColumns.Balance(),
|
||||
StdReportEntryColumns.AccountCode(),
|
||||
StdReportEntryColumns.Bankruptcy(),
|
||||
StdReportEntryColumns.Collector(),
|
||||
StdReportEntryColumns.Collateral(),
|
||||
StdReportEntryColumns.RepoCompany(),
|
||||
StdReportEntryColumns.RepoDuration(),
|
||||
StdReportEntryColumns.DateAssigned(),
|
||||
StdReportEntryColumns.DateRepoed(),
|
||||
StdReportEntryColumns.FilteredStatusChangeDate("Date Sold"))
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving Avg Days Repot to Sold Report");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ReportTableResponse> GetRedemptionCountReport(DateTime lowDateTime, DateTime highDateTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reportData = await _reportingRepository.GetAccountsByStatusChangeInPeriod(RedeemedStatus, lowDateTime, highDateTime);
|
||||
|
||||
return new ReportTableResponse()
|
||||
{
|
||||
ReportTable = ReportTableFactory.CreateHistoricalSummationTable(
|
||||
ReportSumType.WholeNumber,
|
||||
reportData,
|
||||
"Redemptions",
|
||||
lowDateTime,
|
||||
highDateTime,
|
||||
(reportEntry) => 1,
|
||||
(reportEntry) => null,
|
||||
(reportEntry) => reportEntry.FilteredStatusChangeDate,
|
||||
(reportEntry) => $"{reportEntry.RepoId}_{reportEntry.FilteredStatusChangeDate}",
|
||||
false),
|
||||
DataTable = DataTableFormatting.CreateDataTable(
|
||||
reportData,
|
||||
(entity) => entity.RepoId,
|
||||
StdReportEntryColumns.Member(),
|
||||
StdReportEntryColumns.AccountNo(),
|
||||
StdReportEntryColumns.Status(),
|
||||
StdReportEntryColumns.DaysInStatus(),
|
||||
StdReportEntryColumns.DaysCurrentDelq(),
|
||||
StdReportEntryColumns.LastPaymentDate(),
|
||||
StdReportEntryColumns.LastPaymentDate(),
|
||||
StdReportEntryColumns.DelinquentAmount(),
|
||||
StdReportEntryColumns.RepoType(),
|
||||
StdReportEntryColumns.Balance(),
|
||||
StdReportEntryColumns.AccountCode(),
|
||||
StdReportEntryColumns.Bankruptcy(),
|
||||
StdReportEntryColumns.Collector(),
|
||||
StdReportEntryColumns.Collateral(),
|
||||
StdReportEntryColumns.Grade(),
|
||||
StdReportEntryColumns.FloorPrice(),
|
||||
StdReportEntryColumns.Auction(),
|
||||
StdReportEntryColumns.DateEnteredAutoIMS(),
|
||||
StdReportEntryColumns.TotalCharges(),
|
||||
StdReportEntryColumns.DateAssigned(),
|
||||
StdReportEntryColumns.RepoDuration(),
|
||||
StdReportEntryColumns.DateRepoed())
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving Redemption Count Report");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ReportTableResponse> GetAvgDaysDelqWhenAddedReport(DateTime lowDateTime, DateTime highDateTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reportData = await _reportingRepository.GetAccountsByO4RDate(lowDateTime, highDateTime);
|
||||
|
||||
return new ReportTableResponse()
|
||||
{
|
||||
ReportTable = ReportTableFactory.CreateHistoricalSummationTable(
|
||||
ReportSumType.WholeNumberAvg,
|
||||
reportData,
|
||||
"Accounts",
|
||||
lowDateTime,
|
||||
highDateTime,
|
||||
(reportEntry) => reportEntry.DaysDelqWhenAdded,
|
||||
(reportEntry) => null,
|
||||
(reportEntry) => reportEntry.O4RDate,
|
||||
(reportEntry) => reportEntry.RepoId,
|
||||
false),
|
||||
DataTable = DataTableFormatting.CreateDataTable(
|
||||
reportData,
|
||||
(reportRow) => reportRow.RepoId,
|
||||
StdReportEntryColumns.Member(),
|
||||
StdReportEntryColumns.AccountNo(),
|
||||
StdReportEntryColumns.Status(),
|
||||
StdReportEntryColumns.DaysCurrentDelq(),
|
||||
StdReportEntryColumns.DaysDelqWhenAdded(),
|
||||
StdReportEntryColumns.LastPaymentDate(),
|
||||
StdReportEntryColumns.Balance(),
|
||||
StdReportEntryColumns.AccountCode(),
|
||||
StdReportEntryColumns.Bankruptcy(),
|
||||
StdReportEntryColumns.Collector(),
|
||||
StdReportEntryColumns.Collateral(),
|
||||
StdReportEntryColumns.O4RDate())
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving Avg Days Delq When Added Report");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ReportTableResponse> GetCountOfSalesReport(DateTime lowDateTime, DateTime highDateTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reportData = await _reportingRepository.GetAccountsByStatusChangeInPeriod("SOLD", lowDateTime, highDateTime);
|
||||
|
||||
return new ReportTableResponse()
|
||||
{
|
||||
ReportTable = ReportTableFactory.CreateHistoricalSummationTable(
|
||||
ReportSumType.WholeNumber,
|
||||
reportData,
|
||||
"Sold Accounts",
|
||||
lowDateTime,
|
||||
highDateTime,
|
||||
(reportEntry) => 1,
|
||||
(reportEntry) => null,
|
||||
(reportEntry) => reportEntry.FilteredStatusChangeDate,
|
||||
(reportEntry) => reportEntry.RepoId,
|
||||
false),
|
||||
DataTable = DataTableFormatting.CreateDataTable(
|
||||
reportData,
|
||||
(entry) => entry.RepoId,
|
||||
StdReportEntryColumns.Member(),
|
||||
StdReportEntryColumns.AccountNo(),
|
||||
StdReportEntryColumns.Status(),
|
||||
StdReportEntryColumns.DaysInStatus(),
|
||||
StdReportEntryColumns.Balance(),
|
||||
StdReportEntryColumns.AccountCode(),
|
||||
StdReportEntryColumns.Bankruptcy(),
|
||||
StdReportEntryColumns.Collector(),
|
||||
StdReportEntryColumns.Collateral(),
|
||||
StdReportEntryColumns.Auction(),
|
||||
StdReportEntryColumns.GrossSalePrice(),
|
||||
StdReportEntryColumns.GrossPercentFloor(),
|
||||
StdReportEntryColumns.NetPercentFloor(),
|
||||
StdReportEntryColumns.TotalCharges(),
|
||||
StdReportEntryColumns.FilteredStatusChangeDate("Date Sold"))
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving Avg Days Repot to Sold Report");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ReportTableResponse> GetAvgSurplusReport(DateTime lowDateTime, DateTime highDateTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reportData = await _reportingRepository.GetAccountsByDateSoldInAutoIMS(lowDateTime, highDateTime);
|
||||
|
||||
return new ReportTableResponse()
|
||||
{
|
||||
ReportTable = ReportTableFactory.CreateHistoricalSummationTable(
|
||||
ReportSumType.CurrencyAvg,
|
||||
reportData,
|
||||
"Sold Accounts",
|
||||
lowDateTime,
|
||||
highDateTime,
|
||||
(reportEntry) => reportEntry.GrossSalePrice - reportEntry.Balance,
|
||||
(reportEntry) => reportEntry.AuctionName,
|
||||
(reportEntry) => reportEntry.SoldDate,
|
||||
(reportEntry) => reportEntry.RepoId,
|
||||
false),
|
||||
DataTable = DataTableFormatting.CreateDataTable(
|
||||
reportData,
|
||||
(reportRow) => reportRow.RepoId,
|
||||
StdReportEntryColumns.Member(),
|
||||
StdReportEntryColumns.AccountNo(),
|
||||
StdReportEntryColumns.Status(),
|
||||
StdReportEntryColumns.DaysInStatus(),
|
||||
StdReportEntryColumns.GrossSalePrice(),
|
||||
StdReportEntryColumns.Balance(),
|
||||
StdReportEntryColumns.SurplusDeficiency(),
|
||||
StdReportEntryColumns.AccountCode(),
|
||||
StdReportEntryColumns.Bankruptcy(),
|
||||
StdReportEntryColumns.Collector(),
|
||||
StdReportEntryColumns.Collateral(),
|
||||
StdReportEntryColumns.Auction(),
|
||||
StdReportEntryColumns.DateSold())
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving Avg Surplus or Defiency Report");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ReportTableResponse> GetRepoBalanceReport(DateTime lowDateTime, DateTime highDateTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reportData = await _reportingRepository.GetAccountsByDateRepoed(lowDateTime, highDateTime);
|
||||
|
||||
return new ReportTableResponse()
|
||||
{
|
||||
ReportTable = ReportTableFactory.CreateHistoricalSummationTable(
|
||||
ReportSumType.Currency,
|
||||
reportData,
|
||||
"Companies",
|
||||
lowDateTime,
|
||||
highDateTime,
|
||||
(reportEntry) => reportEntry.Balance,
|
||||
(reportEntry) => reportEntry.RepoCompany,
|
||||
(reportEntry) => reportEntry.DateRepoed,
|
||||
(reportEntry) => reportEntry.RepoId),
|
||||
DataTable = DataTableFormatting.CreateDataTable(
|
||||
reportData,
|
||||
(reportRow) => reportRow.RepoId,
|
||||
StdReportEntryColumns.Member(),
|
||||
StdReportEntryColumns.AccountNo(),
|
||||
StdReportEntryColumns.Status(),
|
||||
StdReportEntryColumns.DaysInStatus(),
|
||||
StdReportEntryColumns.RepoType(),
|
||||
StdReportEntryColumns.Balance(),
|
||||
StdReportEntryColumns.AccountCode(),
|
||||
StdReportEntryColumns.Bankruptcy(),
|
||||
StdReportEntryColumns.Collector(),
|
||||
StdReportEntryColumns.Collateral())
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving Repo Balance Report");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ReportTableResponse> GetRedemptionBalanceReport(DateTime lowDateTime, DateTime highDateTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reportData = await _reportingRepository.GetAccountsByStatusChangeInPeriod(RedeemedStatus, lowDateTime, highDateTime);
|
||||
|
||||
return new ReportTableResponse()
|
||||
{
|
||||
ReportTable = ReportTableFactory.CreateHistoricalSummationTable(
|
||||
ReportSumType.Currency,
|
||||
reportData,
|
||||
"Redeemed Accounts",
|
||||
lowDateTime,
|
||||
highDateTime,
|
||||
(reportEntry) => reportEntry.Balance,
|
||||
(reportEntry) => null,
|
||||
(reportEntry) => reportEntry.FilteredStatusChangeDate,
|
||||
(reportEntry) => reportEntry.RepoId,
|
||||
false),
|
||||
DataTable = DataTableFormatting.CreateDataTable(
|
||||
reportData,
|
||||
(reportRow) => reportRow.RepoId,
|
||||
StdReportEntryColumns.Member(),
|
||||
StdReportEntryColumns.AccountNo(),
|
||||
StdReportEntryColumns.Status(),
|
||||
StdReportEntryColumns.DaysInStatus(),
|
||||
StdReportEntryColumns.Balance(),
|
||||
StdReportEntryColumns.AccountCode(),
|
||||
StdReportEntryColumns.Bankruptcy(),
|
||||
StdReportEntryColumns.Collector(),
|
||||
StdReportEntryColumns.Collateral(),
|
||||
StdReportEntryColumns.FilteredStatusChangeDate("Redeemed On"))
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving Redemption Balance Report");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+188
@@ -0,0 +1,188 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Exceptions;
|
||||
using LCU.API.Collections.RepoApi.Factories;
|
||||
using LCU.Shared.Collections.RepoApiLib.Formatting;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Reporting.Responses;
|
||||
using LCU.Shared.Collections.RepoApiLib.Repositories;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.Reporting
|
||||
{
|
||||
public class RepoCompanyReportingService : IRepoCompanyReportingService
|
||||
{
|
||||
private readonly IReportingRepository _reportingRepository;
|
||||
|
||||
public RepoCompanyReportingService(IReportingRepository reportingRepository)
|
||||
{
|
||||
_reportingRepository = reportingRepository;
|
||||
}
|
||||
|
||||
public async Task<ReportTableResponse> GetAssignedBalance(DateTime lowDateTime, DateTime highDateTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reportData = await _reportingRepository.GetAccountsByDateAssigned(lowDateTime, highDateTime);
|
||||
|
||||
return new ReportTableResponse()
|
||||
{
|
||||
ReportTable = ReportTableFactory.CreateHistoricalSummationTable(
|
||||
ReportSumType.Currency,
|
||||
reportData,
|
||||
"Repo Companies",
|
||||
lowDateTime,
|
||||
highDateTime,
|
||||
(reportEntry) => reportEntry.Balance,
|
||||
(reportEntry) => reportEntry.RepoCompany,
|
||||
(reportEntry) => reportEntry.RepoCompanyAssignmentDate,
|
||||
(reportEntry) => reportEntry.CollateralId),
|
||||
DataTable = DataTableFormatting.CreateDataTable(
|
||||
reportData,
|
||||
(entry) => entry.RepoId,
|
||||
StdReportEntryColumns.Member(),
|
||||
StdReportEntryColumns.AccountNo(),
|
||||
StdReportEntryColumns.Status(),
|
||||
StdReportEntryColumns.DaysInStatus(),
|
||||
StdReportEntryColumns.Balance(),
|
||||
StdReportEntryColumns.AccountCode(),
|
||||
StdReportEntryColumns.Bankruptcy(),
|
||||
StdReportEntryColumns.Collateral(),
|
||||
StdReportEntryColumns.RepoCompany(),
|
||||
StdReportEntryColumns.DateAssigned())
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving Repo Company Active Count Report");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ReportTableResponse> GetAssignmentCountReport(DateTime lowDateTime, DateTime highDateTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reportData = await _reportingRepository.GetAccountsByDateAssigned(lowDateTime, highDateTime);
|
||||
|
||||
return new ReportTableResponse()
|
||||
{
|
||||
ReportTable = ReportTableFactory.CreateHistoricalSummationTable(
|
||||
ReportSumType.WholeNumber,
|
||||
reportData,
|
||||
"Repo Companies",
|
||||
lowDateTime,
|
||||
highDateTime,
|
||||
(reportEntry) => 1,
|
||||
(reportEntry) => reportEntry.RepoCompany,
|
||||
(reportEntry) => reportEntry.RepoCompanyAssignmentDate,
|
||||
(reportEntry) => reportEntry.CollateralId),
|
||||
DataTable = DataTableFormatting.CreateDataTable(
|
||||
reportData,
|
||||
(entry) => entry.RepoId,
|
||||
StdReportEntryColumns.Member(),
|
||||
StdReportEntryColumns.AccountNo(),
|
||||
StdReportEntryColumns.Status(),
|
||||
StdReportEntryColumns.DaysInStatus(),
|
||||
StdReportEntryColumns.RepoType(),
|
||||
StdReportEntryColumns.Balance(),
|
||||
StdReportEntryColumns.AccountCode(),
|
||||
StdReportEntryColumns.Bankruptcy(),
|
||||
StdReportEntryColumns.Collector(),
|
||||
StdReportEntryColumns.Collateral(),
|
||||
StdReportEntryColumns.RepoCompany(),
|
||||
StdReportEntryColumns.DateAssigned())
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving Repo Company Active Count Report");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ReportTableResponse> GetAvgDaysAssignedToRepoed(DateTime lowDateTime, DateTime highDateTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reportData = await _reportingRepository.GetAccountsByDateRepoed(lowDateTime, highDateTime);
|
||||
|
||||
return new ReportTableResponse()
|
||||
{
|
||||
ReportTable = ReportTableFactory.CreateHistoricalSummationTable(
|
||||
ReportSumType.WholeNumberAvg,
|
||||
reportData,
|
||||
"Repo Companies",
|
||||
lowDateTime,
|
||||
highDateTime,
|
||||
(reportEntry) => reportEntry.RepoDuration,
|
||||
(reportEntry) => reportEntry.RepoCompany,
|
||||
(reportEntry) => reportEntry.DateRepoed,
|
||||
(reportEntry) => reportEntry.CollateralId,
|
||||
false),
|
||||
DataTable = DataTableFormatting.CreateDataTable(
|
||||
reportData,
|
||||
(reportRow) => reportRow.RepoId,
|
||||
StdReportEntryColumns.Member(),
|
||||
StdReportEntryColumns.AccountNo(),
|
||||
StdReportEntryColumns.Status(),
|
||||
StdReportEntryColumns.DaysInStatus(),
|
||||
StdReportEntryColumns.RepoType(),
|
||||
StdReportEntryColumns.Balance(),
|
||||
StdReportEntryColumns.AccountCode(),
|
||||
StdReportEntryColumns.Bankruptcy(),
|
||||
StdReportEntryColumns.Collateral(),
|
||||
StdReportEntryColumns.RepoCompany(),
|
||||
StdReportEntryColumns.RepoDuration(),
|
||||
StdReportEntryColumns.DateAssigned(),
|
||||
StdReportEntryColumns.DateRepoed())
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving Avg Days Assigned to Repoed Report");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<ReportTableResponse> GetRepoedCountReport(DateTime lowDateTime, DateTime highDateTime)
|
||||
{
|
||||
try
|
||||
{
|
||||
var reportData = await _reportingRepository.GetAccountsByDateRepoed(lowDateTime, highDateTime);
|
||||
|
||||
return new ReportTableResponse()
|
||||
{
|
||||
ReportTable = ReportTableFactory.CreateHistoricalSummationTable(
|
||||
ReportSumType.WholeNumber,
|
||||
reportData,
|
||||
"Repo Companies",
|
||||
lowDateTime,
|
||||
highDateTime,
|
||||
(reportEntry) => 1,
|
||||
(reportEntry) => reportEntry.RepoCompany,
|
||||
(reportEntry) => reportEntry.DateRepoed,
|
||||
(reportEntry) => reportEntry.CollateralId),
|
||||
DataTable = DataTableFormatting.CreateDataTable(
|
||||
reportData,
|
||||
(entry) => entry.RepoId,
|
||||
StdReportEntryColumns.Member(),
|
||||
StdReportEntryColumns.AccountNo(),
|
||||
StdReportEntryColumns.Status(),
|
||||
StdReportEntryColumns.DaysInStatus(),
|
||||
StdReportEntryColumns.RepoType(),
|
||||
StdReportEntryColumns.Balance(),
|
||||
StdReportEntryColumns.AccountCode(),
|
||||
StdReportEntryColumns.Bankruptcy(),
|
||||
StdReportEntryColumns.Collateral(),
|
||||
StdReportEntryColumns.RepoCompany(),
|
||||
StdReportEntryColumns.RepoDuration())
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving Repo Company Active Count Report");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+140
@@ -0,0 +1,140 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlClient;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Exceptions;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Repositories;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public class SavedLocationService : ISavedLocationService
|
||||
{
|
||||
private ISavedLocationRepository _savedLocationRepository;
|
||||
private IUnitOfWork _unitOfWork;
|
||||
|
||||
public SavedLocationService(
|
||||
ISavedLocationRepository savedLocationRepository,
|
||||
IUnitOfWork unitOfWork)
|
||||
{
|
||||
_savedLocationRepository = savedLocationRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<SavedLocation>> ListAllAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _savedLocationRepository.ListAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving Saved Locations");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SavedLocation> GetAsync(int id)
|
||||
{
|
||||
SavedLocation savedLocation;
|
||||
try
|
||||
{
|
||||
savedLocation = await _savedLocationRepository.FindByIdAsync(id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving Saved Location with id: {id}");
|
||||
throw new NotFoundException($"Saved Location with id {id} not found");
|
||||
}
|
||||
|
||||
if (savedLocation == null)
|
||||
{
|
||||
throw new NotFoundException($"Saved Location with id {id} not found");
|
||||
}
|
||||
|
||||
return savedLocation;
|
||||
}
|
||||
|
||||
public async Task<SavedLocation> SaveAsync(SavedLocation savedLocation)
|
||||
{
|
||||
try
|
||||
{
|
||||
var newSavedLocation = await _savedLocationRepository.AddAsync(savedLocation);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return newSavedLocation;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure saving Saved Location {savedLocation}", savedLocation);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SavedLocation> UpdateAsync(SavedLocation savedLocation)
|
||||
{
|
||||
var existing = await _savedLocationRepository.FindByIdAsync(savedLocation.ID);
|
||||
|
||||
if (existing == null)
|
||||
{
|
||||
throw new NotFoundException($"Saved Location with id {savedLocation.ID} not found");
|
||||
}
|
||||
|
||||
existing.Name = savedLocation.Name;
|
||||
existing.LocationType = savedLocation.LocationType;
|
||||
existing.Address_Line_1 = savedLocation.Address_Line_1;
|
||||
existing.Address_Line_2 = savedLocation.Address_Line_2;
|
||||
existing.City = savedLocation.City;
|
||||
existing.State = savedLocation.State;
|
||||
existing.ZIP = savedLocation.ZIP;
|
||||
existing.Country = savedLocation.Country;
|
||||
existing.Phone = savedLocation.Phone;
|
||||
existing.Fax = savedLocation.Fax;
|
||||
|
||||
try
|
||||
{
|
||||
_savedLocationRepository.Update(existing);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return existing;
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Filure updating Saved Location: {savedLocation}", savedLocation);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure updating Saved Location {savedLocation}", savedLocation);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<SavedLocation> DeleteAsync(int id)
|
||||
{
|
||||
var existingSavedLocation = await _savedLocationRepository.FindByIdAsync(id);
|
||||
|
||||
if (existingSavedLocation == null)
|
||||
{
|
||||
throw new NotFoundException($" Saved Location with id {id} not found.");
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
_savedLocationRepository.Remove(existingSavedLocation);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return existingSavedLocation;
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure deleting Saved Location with id: {id}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure deleting Saved Location with id: {id}");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
+592
@@ -0,0 +1,592 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Settings;
|
||||
using LCU.Shared.Collections.RepoApiLib.Repositories.Settings;
|
||||
using LCU.Shared.Collections.RepoApiLib.Repositories;
|
||||
using Serilog;
|
||||
using LCU.API.Collections.RepoApi.Exceptions;
|
||||
using System.Data.SqlClient;
|
||||
using System.Linq;
|
||||
using LCU.API.Collections.CollectionsApiLib.Models;
|
||||
using Microsoft.Extensions.Caching.Memory;
|
||||
using LCU.API.Collections.RepoApi.Configuration;
|
||||
using LCU.API.Collections.RepoApi.DataAccess;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public class SettingsService : ISettingsService
|
||||
{
|
||||
private readonly IAccountTypeRepository _accountTypeRepository;
|
||||
private readonly IAllowedStatusChangeRepository _allowedStatusChangeRepository;
|
||||
private readonly IBodyStyleRepository _bodyStyleRepository;
|
||||
private readonly ICleaningLevelRepository _cleaningLevelRepository;
|
||||
private readonly ICollateralTypeRepository _collateralTypeRepository;
|
||||
private readonly ICollateralTypeToVehicleTypeRepository _collateralTypeToVehicleTypeRepository;
|
||||
private readonly IHoldStatusRepository _holdStatusRepository;
|
||||
private readonly ILoanCategoryRepository _loanCategoryRepository;
|
||||
private readonly ILocationTypeRepository _locationTypeRepository;
|
||||
private readonly IVehicleTypeRepository _vehicleTypeRepository;
|
||||
private readonly IExternalUserRepository _externalUserRepository;
|
||||
private readonly IRepoCompanyRepository _repoCompanyRepository;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
private readonly IMemoryCache _memoryCache;
|
||||
private readonly ICollectionsDao _collectionsDao;
|
||||
private readonly RepoApiConfiguration _config;
|
||||
|
||||
public SettingsService(
|
||||
IAccountTypeRepository accountTypeCodesRepository,
|
||||
IAllowedStatusChangeRepository allowedStatusChangesRepository,
|
||||
IBodyStyleRepository bodyStyleCodesRepository,
|
||||
ICleaningLevelRepository cleaningLevelCodesRepository,
|
||||
ICollateralTypeRepository collateralTypesRepository,
|
||||
ICollateralTypeToVehicleTypeRepository collateralTypesToVehicleTypesRepository,
|
||||
IHoldStatusRepository holdStatusCodesRepository,
|
||||
ILoanCategoryRepository loanCategoriesRepository,
|
||||
ILocationTypeRepository locationTypeCodesRepository,
|
||||
IVehicleTypeRepository vehicleTypeCodesRepository,
|
||||
IExternalUserRepository externalUserRepository,
|
||||
IRepoCompanyRepository repoCompanyRepository,
|
||||
IUnitOfWork unitOfWork, IMemoryCache memoryCache,
|
||||
ICollectionsDao collectionsDao,
|
||||
RepoApiConfiguration configuration)
|
||||
{
|
||||
_accountTypeRepository = accountTypeCodesRepository;
|
||||
_allowedStatusChangeRepository = allowedStatusChangesRepository;
|
||||
_bodyStyleRepository = bodyStyleCodesRepository;
|
||||
_cleaningLevelRepository = cleaningLevelCodesRepository;
|
||||
_collateralTypeRepository = collateralTypesRepository;
|
||||
_collateralTypeToVehicleTypeRepository = collateralTypesToVehicleTypesRepository;
|
||||
_holdStatusRepository = holdStatusCodesRepository;
|
||||
_loanCategoryRepository = loanCategoriesRepository;
|
||||
_locationTypeRepository = locationTypeCodesRepository;
|
||||
_vehicleTypeRepository = vehicleTypeCodesRepository;
|
||||
_externalUserRepository = externalUserRepository;
|
||||
_repoCompanyRepository = repoCompanyRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
_memoryCache = memoryCache;
|
||||
_collectionsDao = collectionsDao;
|
||||
_config = configuration;
|
||||
}
|
||||
|
||||
public async Task<Settings> GetCachedRepoSettingsAsync()
|
||||
{
|
||||
return await _memoryCache.GetOrCreateAsync<Settings>("repoSettings", factory: (ICacheEntry entry) =>
|
||||
{
|
||||
entry.AbsoluteExpiration = DateTime.Now.AddSeconds(_config.SettingsCacheExpirationSeconds);
|
||||
return ListAllSettingsAsync();
|
||||
});
|
||||
}
|
||||
|
||||
public async Task<Settings> ListAllSettingsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
var result = new Settings
|
||||
{
|
||||
AccountTypes = await _accountTypeRepository.ListAllAsync(),
|
||||
AllowedStatusChanges = await _allowedStatusChangeRepository.ListAllAsync(),
|
||||
BodyStyles = await _bodyStyleRepository.ListAllAsync(),
|
||||
CleaningLevels = await _cleaningLevelRepository.ListAllAsync(),
|
||||
CollateralTypes = await _collateralTypeRepository.ListAllAsync(),
|
||||
CollateralTypesToVehicleTypes = await _collateralTypeToVehicleTypeRepository.ListAllAsync(),
|
||||
HoldStatuses = await _holdStatusRepository.ListAllAsync(),
|
||||
LoanCategories = await _loanCategoryRepository.ListAllAsync(),
|
||||
LocationTypes = await _locationTypeRepository.ListAllAsync(),
|
||||
VehicleTypes = await _vehicleTypeRepository.ListAllAsync(),
|
||||
BranchSet = await _collectionsDao.RetrieveBranchSetAsync(),
|
||||
ExternalUsers = await _externalUserRepository.ListAsync(),
|
||||
RepoCompanies = await _repoCompanyRepository.ListAsync() as IReadOnlyCollection<RepoCompany>,
|
||||
TransactionTypeCodes = _config.TransactionTypeCodes,
|
||||
TransactionTypeCodeQueryPrefix = _config.TransactionTypeCodeQueryPrefix,
|
||||
// Convert DateTime format to uppercase for React/Moment JS ToString use
|
||||
TransactionHistoryParameterDateFormat = _config.TransactionHistoryParameterDateFormat.ToUpper(),
|
||||
TransactionHistoryStartDateOffset = _config.TransactionHistoryStartDateOffset,
|
||||
RepoTypeMap = GetRepoTypes()
|
||||
};
|
||||
|
||||
return result;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving Settings.");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AccountType> SaveAsync(AccountType accountType)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _accountTypeRepository.AddAsync(accountType);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
|
||||
return accountType;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure saving Account Type: {accountType}", accountType);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AllowedStatusChange> SaveAsync(AllowedStatusChange statusChange)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _allowedStatusChangeRepository.AddAsync(statusChange);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
|
||||
return statusChange;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure saving Allowed Status Change: {statusChange}", statusChange);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<BodyStyle> SaveAsync(BodyStyle bodyStyle)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _bodyStyleRepository.AddAsync(bodyStyle);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
|
||||
return bodyStyle;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure saving Body Style: {bodyStyle}", bodyStyle);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<CleaningLevel> SaveAsync(CleaningLevel cleaningLevel)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _cleaningLevelRepository.AddAsync(cleaningLevel);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
|
||||
return cleaningLevel;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure saving Cleaning Level: {cleaningLevel}", cleaningLevel);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<CollateralType> SaveAsync(CollateralType collateralType)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _collateralTypeRepository.AddAsync(collateralType);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
|
||||
return collateralType;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure saving Collateral: {collateralType}", collateralType);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<CollateralTypeToVehicleType> SaveAsync(CollateralTypeToVehicleType collateralToVehicle)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _collateralTypeToVehicleTypeRepository.AddAsync(collateralToVehicle);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
|
||||
return collateralToVehicle;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure saving Collateral Type to Vehicle Type: {collateralToVehicle}",
|
||||
collateralToVehicle);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<HoldStatus> SaveAsync(HoldStatus holdStatus)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _holdStatusRepository.AddAsync(holdStatus);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
|
||||
return holdStatus;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure saving Hold Status: {holdStatus}", holdStatus);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<LoanCategory> SaveAsync(LoanCategory loanCategory)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _loanCategoryRepository.AddAsync(loanCategory);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
|
||||
return loanCategory;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure saving Loan Category: {loanCategory}", loanCategory);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<LocationType> SaveAsync(LocationType locationType)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _locationTypeRepository.AddAsync(locationType);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
|
||||
return locationType;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure saving Location Type: {locationType}", locationType);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<VehicleType> SaveAsync(VehicleType vehicleType)
|
||||
{
|
||||
try
|
||||
{
|
||||
await _vehicleTypeRepository.AddAsync(vehicleType);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
|
||||
return vehicleType;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure saving Vehicle Type: {vehicleType}", vehicleType);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AccountType> UpdateAsync(AccountType accountType)
|
||||
{
|
||||
var existingAccountType = await _accountTypeRepository.FindByIdAsync(accountType.ID);
|
||||
|
||||
if (existingAccountType == null)
|
||||
{
|
||||
throw new NotFoundException($" Account Type with id {accountType.ID} not found.");
|
||||
}
|
||||
|
||||
existingAccountType.AccountTypeCode = accountType.AccountTypeCode;
|
||||
existingAccountType.Description = accountType.Description;
|
||||
existingAccountType.IsVisible = accountType.IsVisible;
|
||||
|
||||
try
|
||||
{
|
||||
_accountTypeRepository.Update(existingAccountType);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return (existingAccountType);
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure updating Account Type: {accountType}", accountType);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure updating Account Type: {accountType}", accountType);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AllowedStatusChange> UpdateAsync(AllowedStatusChange allowedStatusChange)
|
||||
{
|
||||
var existingStatusChange = await _allowedStatusChangeRepository.FindByIdAsync(allowedStatusChange.ID);
|
||||
|
||||
if (existingStatusChange == null)
|
||||
{
|
||||
throw new NotFoundException($" Allowed Status Change with id {allowedStatusChange.ID} not found.");
|
||||
}
|
||||
|
||||
existingStatusChange.CurrentStatus = allowedStatusChange.CurrentStatus;
|
||||
existingStatusChange.AllowedChangeStatus = allowedStatusChange.AllowedChangeStatus;
|
||||
|
||||
try
|
||||
{
|
||||
_allowedStatusChangeRepository.Update(existingStatusChange);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return (existingStatusChange);
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure updating Allowed Status Change: {allowedStatusChange}",
|
||||
allowedStatusChange);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure updating Allowed Status Change {allowedStatusChange}", allowedStatusChange);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<BodyStyle> UpdateAsync(BodyStyle bodyStyle)
|
||||
{
|
||||
var existingBodyStyle = await _bodyStyleRepository.FindByIdAsync(bodyStyle.Code);
|
||||
|
||||
if (existingBodyStyle == null)
|
||||
{
|
||||
throw new NotFoundException($" Body Style with code {bodyStyle.Code} not found.");
|
||||
}
|
||||
|
||||
existingBodyStyle.Description = bodyStyle.Description;
|
||||
|
||||
try
|
||||
{
|
||||
_bodyStyleRepository.Update(existingBodyStyle);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return (existingBodyStyle);
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure updating Body Style: {bodyStyle}", bodyStyle);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure updating Body Style: {bodyStyle}", bodyStyle);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<CleaningLevel> UpdateAsync(CleaningLevel cleaningLevel)
|
||||
{
|
||||
var existingCleaningLevel = await _cleaningLevelRepository.FindByIdAsync(cleaningLevel.Code);
|
||||
|
||||
if (existingCleaningLevel == null)
|
||||
{
|
||||
throw new NotFoundException($" Cleaning Level with code {cleaningLevel.Code} not found.");
|
||||
}
|
||||
|
||||
existingCleaningLevel.Description = cleaningLevel.Description;
|
||||
|
||||
try
|
||||
{
|
||||
_cleaningLevelRepository.Update(existingCleaningLevel);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return (existingCleaningLevel);
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure updating Cleaning Level: {cleaningLevel}", cleaningLevel);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure updating Cleaning Level: {cleaningLevel}", cleaningLevel);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<CollateralType> UpdateAsync(CollateralType collateralType)
|
||||
{
|
||||
var existingCollateralType = await _collateralTypeRepository.FindByIdAsync(collateralType.ID);
|
||||
|
||||
if (existingCollateralType == null)
|
||||
{
|
||||
throw new NotFoundException($" Collateral Type with code {collateralType.ID} not found.");
|
||||
}
|
||||
|
||||
existingCollateralType.Description = collateralType.Description;
|
||||
existingCollateralType.LookupHandler = collateralType.LookupHandler;
|
||||
|
||||
try
|
||||
{
|
||||
_collateralTypeRepository.Update(existingCollateralType);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return (existingCollateralType);
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure updating Collateral Type: {collateralType}", collateralType);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure updating Collateral Type: {collateralType}", collateralType);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<CollateralTypeToVehicleType> UpdateAsync(CollateralTypeToVehicleType collateralToVehicle)
|
||||
{
|
||||
var existingCollateralToVehicle =
|
||||
await _collateralTypeToVehicleTypeRepository.FindByIdAsync(collateralToVehicle.ID);
|
||||
|
||||
if (existingCollateralToVehicle == null)
|
||||
{
|
||||
throw new NotFoundException(
|
||||
$" Collateral Type To Vehicle Type with id {collateralToVehicle.ID} not found.");
|
||||
}
|
||||
|
||||
existingCollateralToVehicle.CollateralTypeId = collateralToVehicle.CollateralTypeId;
|
||||
existingCollateralToVehicle.VehicleTypeId = collateralToVehicle.VehicleTypeId;
|
||||
|
||||
try
|
||||
{
|
||||
_collateralTypeToVehicleTypeRepository.Update(existingCollateralToVehicle);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return (existingCollateralToVehicle);
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure updating Collateral Type To Vehicle Type: {collateralToVehicle}",
|
||||
collateralToVehicle);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure updating Collateral Type To Vehicle Type: {collateralToVehicle}",
|
||||
collateralToVehicle);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<HoldStatus> UpdateAsync(HoldStatus holdStatus)
|
||||
{
|
||||
var existingHoldStatus = await _holdStatusRepository.FindByIdAsync(holdStatus.Code);
|
||||
|
||||
if (existingHoldStatus == null)
|
||||
{
|
||||
throw new NotFoundException($" Hold Status with code {holdStatus.Code} not found.");
|
||||
}
|
||||
|
||||
existingHoldStatus.Description = holdStatus.Description;
|
||||
|
||||
try
|
||||
{
|
||||
_holdStatusRepository.Update(existingHoldStatus);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return (existingHoldStatus);
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure updating Hold Status: {holdStatus}", holdStatus);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure updating Hold Status: {holdStatus}", holdStatus);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<LoanCategory> UpdateAsync(LoanCategory loanCategory)
|
||||
{
|
||||
var existingLoanCategory = await _loanCategoryRepository.FindByIdAsync(loanCategory.ID);
|
||||
|
||||
if (existingLoanCategory == null)
|
||||
{
|
||||
throw new NotFoundException($" Loan Category with code {loanCategory.ID} not found.");
|
||||
}
|
||||
|
||||
existingLoanCategory.CategoryName = loanCategory.CategoryName;
|
||||
|
||||
try
|
||||
{
|
||||
_loanCategoryRepository.Update(existingLoanCategory);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return (existingLoanCategory);
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure updating Loan Category: {loanCategory}", loanCategory);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure updating Loan Category: {loanCategory}", loanCategory);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<LocationType> UpdateAsync(LocationType locationType)
|
||||
{
|
||||
var existingLocationType = await _locationTypeRepository.FindByIdAsync(locationType.Code);
|
||||
|
||||
if (existingLocationType == null)
|
||||
{
|
||||
throw new NotFoundException($" Location Type with code {locationType.Code} not found.");
|
||||
}
|
||||
|
||||
existingLocationType.Description = locationType.Description;
|
||||
|
||||
try
|
||||
{
|
||||
_locationTypeRepository.Update(existingLocationType);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return (existingLocationType);
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure updating Location Type: {locationType}", locationType);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure updating Location Type: {locationType}", locationType);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<VehicleType> UpdateAsync(VehicleType vehicleType)
|
||||
{
|
||||
var existingVehicleType = await _vehicleTypeRepository.FindByIdAsync(vehicleType.Code);
|
||||
|
||||
if (existingVehicleType == null)
|
||||
{
|
||||
throw new NotFoundException($" Vehicle Type with code {vehicleType.Code} not found.");
|
||||
}
|
||||
|
||||
existingVehicleType.Description = vehicleType.Description;
|
||||
|
||||
try
|
||||
{
|
||||
_vehicleTypeRepository.Update(existingVehicleType);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return (existingVehicleType);
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure updating Vehicle Type: {vehicleType}", vehicleType);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure updating Vehicle Type: {vehicleType}", vehicleType);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task SyncExternalUsersAsync(IReadOnlyCollection<ExternalUser> users)
|
||||
{
|
||||
await _externalUserRepository.SynchronizeSet(users);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
}
|
||||
|
||||
private static Dictionary<int, string> GetRepoTypes()
|
||||
{
|
||||
return Enum.GetValues(typeof(RepoType))
|
||||
.Cast<RepoType>()
|
||||
.ToDictionary(type => (int)type, type => type.ToString() );
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using LCU.API.Collections.CollectionsApiLib.Models;
|
||||
using LCU.API.Collections.RepoApi.Exceptions;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public class StatusService
|
||||
{
|
||||
/**
|
||||
* Convert a Stage from a case to a RepoAccount status.
|
||||
*/
|
||||
public static string ConvertStatus(RepoStatus oldStage)
|
||||
{
|
||||
switch (oldStage)
|
||||
{
|
||||
case RepoStatus.PSKP: return "PSKP";
|
||||
case RepoStatus.REHO: return "REHO";
|
||||
case RepoStatus.RECA: return "RECA";
|
||||
case RepoStatus.O4R: return "O4R";
|
||||
case RepoStatus.REPO: return "REPO";
|
||||
case RepoStatus.REDEEMED: return "REDEEMED";
|
||||
case RepoStatus.SOLD: return "SOLD";
|
||||
case RepoStatus.NOTACTIVE: return "SOLD";
|
||||
case RepoStatus.INELIGIBLE: throw new IneligibleAccountException("CaseStage", "Not a Repo Case Stage.");
|
||||
// case RepoStatus.UNKNOWN is meant to fall through to below.
|
||||
}
|
||||
|
||||
Log.Fatal($"Attempt to convert status {oldStage}, but was not known.");
|
||||
// The account is not necessarily ineligible, but is missing a requirement, the stage.
|
||||
// This covers the problem of Temenos not providing a Case Stage Change event, and only
|
||||
// a Case Creation event. The stage won't be available yet, so throw a MissingRequirement
|
||||
// causing it to be retried on the hour - it should then run fine.
|
||||
throw new MissingRequirementException("Case Stage", $"A valid case stage is required, {oldStage} is not valid.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+104
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.SqlClient;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Exceptions;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Repositories;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services
|
||||
{
|
||||
public class VehicleFloorPricingParametersService : IVehicleFloorPricingParametersService
|
||||
{
|
||||
private readonly IVehicleFloorPricingParametersRepository _parametersRepository;
|
||||
private readonly IUnitOfWork _unitOfWork;
|
||||
|
||||
public VehicleFloorPricingParametersService(
|
||||
IVehicleFloorPricingParametersRepository parametersRepository,
|
||||
IUnitOfWork unitOfWork)
|
||||
{
|
||||
_parametersRepository = parametersRepository;
|
||||
_unitOfWork = unitOfWork;
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<VehicleFloorPricingParameters>> ListAll()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _parametersRepository.ListAllAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Failure retrieving VehicleFloorPricingParameters");
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<VehicleFloorPricingParameters> Get(int id)
|
||||
{
|
||||
VehicleFloorPricingParameters parameters;
|
||||
try
|
||||
{
|
||||
parameters = await _parametersRepository.FindByIdAsync(id);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving VehicleFloorPricingParameters: {id}", id);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
|
||||
if (parameters == null)
|
||||
{
|
||||
throw new NotFoundException($"VehicleFloorPricingParameters with id {id} not found.");
|
||||
}
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public async Task<VehicleFloorPricingParameters> UpdateAsync(VehicleFloorPricingParameters parameters)
|
||||
{
|
||||
VehicleFloorPricingParameters existing = null;
|
||||
try
|
||||
{
|
||||
existing = await _parametersRepository.FindByIdAsync(parameters.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure retrieving existing VehicleFloorPricingParameters with Id: {parameters.Id}", parameters.Id);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
|
||||
if (existing == null)
|
||||
{
|
||||
throw new NotFoundException($"Vehicle Floor Pricing Parameters with id {parameters.Id} not found.");
|
||||
}
|
||||
|
||||
existing.LowCondition = parameters.LowCondition;
|
||||
existing.HighCondition = parameters.HighCondition;
|
||||
existing.NADAFirstValue = parameters.NADAFirstValue;
|
||||
existing.NADASecondValue = parameters.NADASecondValue;
|
||||
existing.MMRAdjustedValue = parameters.MMRAdjustedValue;
|
||||
existing.PercentageOf = parameters.PercentageOf;
|
||||
existing.ValuationServiceType = parameters.ValuationServiceType;
|
||||
|
||||
try
|
||||
{
|
||||
_parametersRepository.Update(existing);
|
||||
await _unitOfWork.CompleteAsync();
|
||||
return (existing);
|
||||
}
|
||||
catch (SqlException ex)
|
||||
{
|
||||
Log.Error(ex, $"SQL Failure updating Vehicle Floor Pricing Parameters: {parameters}", parameters);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Failure updating Vehicle Floor Pricing Parameters: {parameters}", parameters);
|
||||
throw new ServerErrorException();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user