Files
2026-04-20 14:47:24 -05:00

461 lines
20 KiB
C#

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;
}
}
}