This commit is contained in:
Zeeshaun
2026-04-20 14:47:24 -05:00
parent 89057eceea
commit 8b3ea92501
485 changed files with 71042 additions and 0 deletions
@@ -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;
}
}
}
@@ -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();
}
}
}
@@ -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;
}
}
}
@@ -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>();
}
}
}
@@ -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;
}
}
}
@@ -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;
}
}
}
@@ -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();
}
}
@@ -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();
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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();
}
}
@@ -0,0 +1,9 @@
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Services.AutoIms
{
public interface IAutoImsNoteProcessorService
{
AuctionAssignmentNote ImportSingleNoteRow(AuctionAssignmentNote item);
}
}
@@ -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);
}
}
@@ -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);
}
}
@@ -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));
}
}
}
@@ -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;
}
}
}