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
+352
View File
@@ -0,0 +1,352 @@
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
##
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
# User-specific files
*.rsuser
*.suo
*.user
*.userosscache
*.sln.docstates
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
# Rider
.idea
# Mono auto generated files
mono_crash.*
# Build results
[Dd]ebug/
[Dd]ebugPublic/
[Rr]elease/
[Rr]eleases/
x64/
x86/
[Aa][Rr][Mm]/
[Aa][Rr][Mm]64/
bld/
[Bb]in/
[Oo]bj/
[Ll]og/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
#wwwroot/
# Visual Studio 2017 auto generated files
Generated\ Files/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
# NUnit
*.VisualState.xml
TestResult.xml
nunit-*.xml
# Build Results of an ATL Project
[Dd]ebugPS/
[Rr]eleasePS/
dlldata.c
# Benchmark Results
BenchmarkDotNet.Artifacts/
# .NET Core
project.lock.json
project.fragment.lock.json
artifacts/
# StyleCop
StyleCopReport.xml
# Files built by Visual Studio
*_i.c
*_p.c
*_h.h
*.ilk
*.meta
*.obj
*.iobj
*.pch
*.pdb
*.ipdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*_wpftmp.csproj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.svclog
*.scc
# Chutzpah Test files
_Chutzpah*
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opendb
*.opensdf
*.sdf
*.cachefile
*.VC.db
*.VC.VC.opendb
# Visual Studio profiler
*.psess
*.vsp
*.vspx
*.sap
# Visual Studio Trace Files
*.e2e
# TFS 2012 Local Workspace
$tf/
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
*.DotSettings.user
# JustCode is a .NET coding add-in
.JustCode
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# AxoCover is a Code Coverage Tool
.axoCover/*
!.axoCover/settings.json
# Visual Studio code coverage results
*.coverage
*.coveragexml
# NCrunch
_NCrunch_*
.*crunch*.local.xml
nCrunchTemp_*
# MightyMoose
*.mm.*
AutoTest.Net/
# Web workbench (sass)
.sass-cache/
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.[Pp]ublish.xml
*.azurePubxml
# Note: Comment the next line if you want to checkin your web deploy settings,
# but database connection strings (with potential passwords) will be unencrypted
*.pubxml
*.publishproj
# Microsoft Azure Web App publish settings. Comment the next line if you want to
# checkin your Azure Web App publish settings, but sensitive information contained
# in these scripts will be unencrypted
PublishScripts/
# NuGet Packages
*.nupkg
# NuGet Symbol Packages
*.snupkg
# The packages folder can be ignored because of Package Restore
**/[Pp]ackages/*
# except build/, which is used as an MSBuild target.
!**/[Pp]ackages/build/
# Uncomment if necessary however generally it will be regenerated when needed
#!**/[Pp]ackages/repositories.config
# NuGet v3's project.json files produces more ignorable files
*.nuget.props
*.nuget.targets
# Microsoft Azure Build Output
csx/
*.build.csdef
# Microsoft Azure Emulator
ecf/
rcf/
# Windows Store app package directories and files
AppPackages/
BundleArtifacts/
Package.StoreAssociation.xml
_pkginfo.txt
*.appx
*.appxbundle
*.appxupload
# Visual Studio cache files
# files ending in .cache can be ignored
*.[Cc]ache
# but keep track of directories ending in .cache
!?*.[Cc]ache/
# Others
ClientBin/
~$*
*~
*.dbmdl
*.dbproj.schemaview
*.jfm
*.pfx
*.publishsettings
orleans.codegen.cs
# Including strong name files can present a security risk
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
#*.snk
# Since there are multiple workflows, uncomment next line to ignore bower_components
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
#bower_components/
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file
# to a newer Visual Studio version. Backup files are not needed,
# because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
ServiceFabricBackup/
*.rptproj.bak
# SQL Server files
*.mdf
*.ldf
*.ndf
# Business Intelligence projects
*.rdl.data
*.bim.layout
*.bim_*.settings
*.rptproj.rsuser
*- [Bb]ackup.rdl
*- [Bb]ackup ([0-9]).rdl
*- [Bb]ackup ([0-9][0-9]).rdl
# Microsoft Fakes
FakesAssemblies/
# GhostDoc plugin setting file
*.GhostDoc.xml
# Node.js Tools for Visual Studio
.ntvs_analysis.dat
node_modules/
# Visual Studio 6 build log
*.plg
# Visual Studio 6 workspace options file
*.opt
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
*.vbw
# Visual Studio LightSwitch build output
**/*.HTMLClient/GeneratedArtifacts
**/*.DesktopClient/GeneratedArtifacts
**/*.DesktopClient/ModelManifest.xml
**/*.Server/GeneratedArtifacts
**/*.Server/ModelManifest.xml
_Pvt_Extensions
# Paket dependency manager
.paket/paket.exe
paket-files/
# FAKE - F# Make
.fake/
# CodeRush personal settings
.cr/personal
# Python Tools for Visual Studio (PTVS)
__pycache__/
*.pyc
# Cake - Uncomment if you are using it
# tools/**
# !tools/packages.config
# Tabs Studio
*.tss
# Telerik's JustMock configuration file
*.jmconfig
# BizTalk build output
*.btp.cs
*.btm.cs
*.odx.cs
*.xsd.cs
# OpenCover UI analysis results
OpenCover/
# Azure Stream Analytics local run output
ASALocalRun/
# MSBuild Binary and Structured Log
*.binlog
# NVidia Nsight GPU debugger configuration file
*.nvuser
# MFractors (Xamarin productivity tool) working folder
.mfractor/
# Local History for Visual Studio
.localhistory/
# BeatPulse healthcheck temp database
healthchecksdb
# Backup folder for Package Reference Convert tool in Visual Studio 2017
MigrationBackup/
@@ -0,0 +1,41 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 15
VisualStudioVersion = 15.0.28307.645
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LCU.API.Collections.RepoApi", "LCU.API.Collections.RepoApi\LCU.API.Collections.RepoApi.csproj", "{7D12B7C0-7967-41CA-A442-A3F78182F0B5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LCU.API.Collections.RepoApiTests", "LCU.API.Collections.RepoApiTests\LCU.API.Collections.RepoApiTests.csproj", "{EA9E0245-8AA9-4E33-8507-E7C41EFC5DA5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LCU.Shared.Collections.RepoApiLib", "LCU.Shared.Collections.RepoApiLib\LCU.Shared.Collections.RepoApiLib.csproj", "{1E93535C-B421-4D9B-97C6-6F4FD0308B3C}"
EndProject
Project("{00D1A9C2-B5F0-4AF3-8072-F6C62B433612}") = "LCU.API.Collections.RepoApiSql", "LCU.API.Collections.RepoApiSql\LCU.API.Collections.RepoApiSql.sqlproj", "{33A79137-9040-4749-B89D-1055657C3745}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7D12B7C0-7967-41CA-A442-A3F78182F0B5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7D12B7C0-7967-41CA-A442-A3F78182F0B5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7D12B7C0-7967-41CA-A442-A3F78182F0B5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7D12B7C0-7967-41CA-A442-A3F78182F0B5}.Release|Any CPU.Build.0 = Release|Any CPU
{EA9E0245-8AA9-4E33-8507-E7C41EFC5DA5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EA9E0245-8AA9-4E33-8507-E7C41EFC5DA5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EA9E0245-8AA9-4E33-8507-E7C41EFC5DA5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EA9E0245-8AA9-4E33-8507-E7C41EFC5DA5}.Release|Any CPU.Build.0 = Release|Any CPU
{1E93535C-B421-4D9B-97C6-6F4FD0308B3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1E93535C-B421-4D9B-97C6-6F4FD0308B3C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1E93535C-B421-4D9B-97C6-6F4FD0308B3C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1E93535C-B421-4D9B-97C6-6F4FD0308B3C}.Release|Any CPU.Build.0 = Release|Any CPU
{33A79137-9040-4749-B89D-1055657C3745}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{33A79137-9040-4749-B89D-1055657C3745}.Release|Any CPU.ActiveCfg = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {458D6DB8-766A-4013-8923-6571AB24E3EF}
EndGlobalSection
EndGlobal
@@ -0,0 +1,12 @@
namespace LCU.API.Collections.RepoApi.Configuration
{
/**
* These feature flags should match what is in the appsettings under "FeatureFlags" to prevent Magic Strings.
*/
public static class FeatureFlags
{
public const string EnableControllers = "EnableControllers";
public const string RunRepoHostedService = "RunRepoHostedService";
public const string RunAutoImsHostedService = "RunAutoImsHostedService";
}
}
@@ -0,0 +1,60 @@
using LCU.Shared.Collections.RepoApiLib.Models.AutoIms;
using System.Collections.Generic;
using LCU.Shared.Realtime.QueueLib.Models;
using System.Runtime.CompilerServices;
namespace LCU.API.Collections.RepoApi.Configuration
{
public class RepoApiConfiguration : IHostedTaskSchedulerConfig
{
public string ExchangeName { get; set; }
public IReadOnlyCollection<ScheduledTaskConfig> TaskConfigs { get; set; }
public string OrganizationCode { get; set; }
public int SettingsCacheExpirationSeconds { get; set; }
public List<string> ProgrammingRecipients { get; set; }
public List<string> RepoManagerAdminRecipients { get; set; }
public List<string> FloorPriceAcceptedRecipients { get; set; }
public List<string> CollectionsClerkRecipients { get; set; }
public List<string> CollectionsRecipients { get; set; }
public List<string> CreditDebitsRecipients { get; set; }
public List<string> TransactionTypeCodes { get; set; }
public string TransactionTypeCodeQueryPrefix { get; set; }
public string TransactionHistoryParameterDateFormat { get; set; }
public int TransactionHistoryStartDateOffset { get; set; }
public string NotificationsEmailUrl { get; set; }
public string RepoManagerAccountUrl { get; set; }
// URLs for data from Collections (Intellect/CORS/Temenos)
public string CollectionsLetterUrl { get; set; }
public string CollectionAccountUrl { get; set; }
// URLs for data from Core.
public string CoreLoanUrl { get; set; }
public string CoreCollateralUrl { get; set; }
public string CoreMemberUrl { get; set; }
public string CoreLoanTransactions { get; set; }
public string CoreBranchSetUrl { get; set; }
// Auto IMS Import
public FtpConfiguration FtpConfiguration { get; set; }
public string RemoteFilePath { get; set; }
public string LocalDirectoryPath { get; set; }
public string ConditionReportUrl { get; set; }
public bool MoveProcessedFiles { get; set; }
public bool DownloadNewFiles { get; set; }
public bool CheckForLocalFiles { get; set; }
public string CollectionsConfigUrl { get; set; }
public IReadOnlyCollection<ScheduledTaskConfig> GetTaskConfigurations()
{
return TaskConfigs;
}
public string GetExchangeName()
{
return ExchangeName;
}
public IDictionary<string, QueueConsumerConfig> QueueConsumerConfigs { get; set; }
}
}
@@ -0,0 +1,242 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using LCU.API.Collections.RepoApi.Configuration;
using LCU.API.Collections.RepoApi.Exceptions;
using LCU.API.Collections.RepoApi.Extensions;
using LCU.API.Collections.RepoApi.Models.Filters;
using LCU.API.Collections.RepoApi.Models.Requests;
using LCU.API.Collections.RepoApi.Models.Responses;
using LCU.API.Collections.RepoApi.Services;
using LCU.Shared.Collections.RepoApiLib.Exceptions;
using LCU.Shared.Collections.RepoApiLib.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement.Mvc;
using Serilog;
namespace LCU.API.Collections.RepoApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
[FeatureGate(FeatureFlags.EnableControllers)]
public class AuctionAssignmentController : RepoControllerBase
{
private readonly IAuctionAssignmentService _auctionAssignmentService;
private readonly IFloorPricingService _floorPricingService;
public AuctionAssignmentController(
IAuctionAssignmentService auctionAssignmentService,
IFloorPricingService floorPricingService,
IHttpContextAccessor httpContextAccessor) :
base(httpContextAccessor)
{
_auctionAssignmentService = auctionAssignmentService;
_floorPricingService = floorPricingService;
}
// GET: api/AuctionAssignment
[HttpGet(Name = "GetAllAuctionAssignments")]
[ProducesResponseType(typeof(PagedResponse<AuctionAssignment>), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> Get([FromQuery] AuctionAssignmentFilter filter)
{
try
{
var searchResult = await _auctionAssignmentService.ListByFilterAsync(filter);
var result = new PagedResponse<AuctionAssignment>(searchResult, filter, this.Url, Request);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/AuctionAssignment/5
[HttpGet("{id}", Name = "GetAuctionAssignmentById")]
[ProducesResponseType(typeof(AuctionAssignment), 200)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetById(int id)
{
try
{
if (id <= 0)
{
return BadRequest("Invalid ID");
}
var result = await _auctionAssignmentService.GetByIdAsync(id);
return Ok(result);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// Get: api/AuctionAssignment/repoId/7
[HttpGet("repoId/{repoId}", Name = "GetAuctionAssignmentsByRepoId")]
[ProducesResponseType(typeof(IEnumerable<AuctionAssignment>), 200)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetByRepoId(int repoId)
{
try
{
if (repoId <= 0)
{
return BadRequest("Invalid ID");
}
var result = await _auctionAssignmentService.GetByRepoIdAsync(repoId);
return Ok(result);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/AuctionAssignment/5/FloorPrice
[HttpGet("{id}/FloorPrice", Name = "GetAuctionAssignmentFloorPriceById")]
[ProducesResponseType(typeof(FloorPriceCalculationResultSet), 200)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(500)]
public IActionResult GetAuctionAssignmentFloorPriceById(int id)
{
try
{
if (id <= 0)
{
return BadRequest("Invalid ID");
}
var result = _floorPricingService.DetermineFloorPrice(id);
return Ok(result);
}
catch (NotFoundException nfe)
{
Log.Error(nfe, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/AuctionAssignment
[HttpPost]
[ProducesResponseType(typeof(AuctionAssignmentPostRequest), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> Post([FromBody] AuctionAssignmentPostRequest auctionAssignmentPostRequest)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var result = await _auctionAssignmentService.SaveAsync(auctionAssignmentPostRequest);
return Ok(result);
}
catch (WebException we)
{
Log.Error(we, $"Failure in {this.GetType().Name} at [AuctionAssignmentPostRequest]");
return StatusCode(StatusCodes.Status500InternalServerError, we.Message);
}
catch (DuplicateResourceException)
{
return StatusCode(StatusCodes.Status409Conflict, "An active auction assignment already exists. You must remove the auction before creating a new one.");
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to add auction to AutoIMS.");
}
}
// PUT: api/AuctionAssignment/5
[HttpPut("{id}")]
[ProducesResponseType(typeof(AuctionAssignment), 200)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> Put(int id, [FromBody] AuctionAssignment auctionAssignment)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
if (id <= 0 || id != auctionAssignment.ID)
{
return BadRequest("Invalid ID");
}
var result = await _auctionAssignmentService.UpdateAsync(auctionAssignment);
return Ok(result);
}
catch (NotFoundException nfe)
{
Log.Error(nfe, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// DELETE: api/ApiWithActions/5
[HttpDelete("{id}")]
[ProducesResponseType(typeof(AuctionAssignment), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> Delete(int id)
{
try
{
if (id <= 0)
{
return BadRequest("Invalid ID");
}
var deletedRepoCompany = await _auctionAssignmentService.RemoveAuction(id);
return Ok(deletedRepoCompany);
}
catch (WebException we)
{
return StatusCode(StatusCodes.Status500InternalServerError, we.Message);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError, "Failed to remove auction from AutoIMS.");
}
}
}
}
@@ -0,0 +1,151 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using LCU.API.Collections.RepoApi.Configuration;
using LCU.API.Collections.RepoApi.Exceptions;
using LCU.API.Collections.RepoApi.Extensions;
using LCU.API.Collections.RepoApi.Services;
using LCU.Shared.Collections.RepoApiLib.Models;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement.Mvc;
using Serilog;
namespace LCU.API.Collections.RepoApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
[FeatureGate(FeatureFlags.EnableControllers)]
public class AuctionHouseController : RepoControllerBase
{
private readonly IAuctionHouseService _auctionHouseService;
public AuctionHouseController(
IAuctionHouseService auctionHouseService,
IHttpContextAccessor httpContextAccessor)
: base( httpContextAccessor)
{
_auctionHouseService = auctionHouseService;
}
[HttpGet(Name = "GetAllAuctionHouses")]
[ProducesResponseType(typeof(IEnumerable<AuctionHouse>), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> Get()
{
try
{
var auctions = await _auctionHouseService.ListAllAsync();
return Ok(auctions);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpGet("{id}", Name = "GetAuctionHouseById")]
[ProducesResponseType(typeof(AuctionHouse), 200)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetById(string id)
{
try
{
var auction = await _auctionHouseService.GetAsync(id);
return Ok(auction);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPost]
[ProducesResponseType(typeof(AuctionHouse), 200)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")]
public async Task<IActionResult> Post([FromBody] AuctionHouse auction)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var newAuction = await _auctionHouseService.SaveAsync(auction);
return Ok(newAuction);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPut("{id}")]
[ProducesResponseType(typeof(AuctionHouse), 200)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")]
public async Task<IActionResult> Put(string id, [FromBody] AuctionHouse auction)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
if (id != auction.AuctionID)
{
return BadRequest("Invalid ID");
}
var updatedAuction = await _auctionHouseService.UpdateAsync(auction);
return Ok(updatedAuction);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpDelete("{id}")]
[ProducesResponseType(typeof(AuctionHouse), 200)]
[ProducesResponseType(500)]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")]
public async Task<IActionResult> Delete(string id)
{
try
{
var deletedAuction = await _auctionHouseService.DeleteAsync(id);
return Ok(deletedAuction);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
}
}
@@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using LCU.API.Collections.RepoApi.Configuration;
using LCU.API.Collections.RepoApi.Exceptions;
using LCU.API.Collections.RepoApi.Extensions;
using LCU.API.Collections.RepoApi.Models.Filters;
using LCU.API.Collections.RepoApi.Models.Responses;
using LCU.API.Collections.RepoApi.Services;
using LCU.Shared.Collections.RepoApiLib.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement.Mvc;
using Serilog;
namespace LCU.API.Collections.RepoApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
[FeatureGate(FeatureFlags.EnableControllers)]
public class AuditRecordController : RepoControllerBase
{
private readonly IAuditRecordService _auditRecordService;
public AuditRecordController(
IAuditRecordService repoAccountService,
IHttpContextAccessor httpContextAccessor) : base(httpContextAccessor)
{
_auditRecordService = repoAccountService;
}
// GET: api/AuditRecord
[HttpGet(Name = "GetAllAuditRecords")]
[ProducesResponseType(typeof(PagedResponse<AuditRecord>), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> Get([FromQuery] PageFilter filter)
{
try
{
var searchResult = await _auditRecordService.ListAll(filter);
var result = new PagedResponse<AuditRecord>(searchResult, filter, Url, Request);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/AuditRecord/5
[HttpGet("{id}", Name = "GetAuditRecordById")]
[ProducesResponseType(typeof(RepoAccount), 200)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetById(int id)
{
try
{
if (id <= 0)
{
return BadRequest("Invalid ID");
}
var result = await _auditRecordService.Get(id);
return Ok(result);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/RepoAccount
[HttpPost]
[ProducesResponseType(typeof(RepoAccount), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> Post([FromBody] AuditRecord repoAccount)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var result = await _auditRecordService.SaveAsync(repoAccount);
return Ok( result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
}
}
@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using LCU.API.Collections.RepoApi.Configuration;
using LCU.API.Collections.RepoApi.Exceptions;
using LCU.API.Collections.RepoApi.Extensions;
using LCU.API.Collections.RepoApi.Services;
using LCU.Shared.Collections.RepoApiLib.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement.Mvc;
using Serilog;
namespace LCU.API.Collections.RepoApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
[FeatureGate(FeatureFlags.EnableControllers)]
public class CollateralController : RepoControllerBase
{
private readonly ICollateralService _collateralService;
public CollateralController(
ICollateralService collateralService,
IHttpContextAccessor httpContextAccessor) :
base(httpContextAccessor)
{
_collateralService = collateralService;
}
// GET: api/Collateral
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<Collateral>), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> Get()
{
try
{
var results = await _collateralService.ListAll();
return Ok(results);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/Collateral/5
[HttpGet("{id}", Name = "GetCollateralById")]
[ProducesResponseType(typeof(Collateral), 200)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetById(int id)
{
try
{
if (id <= 0)
{
return BadRequest("Invalid ID");
}
var result = await _collateralService.Get(id);
return Ok(result);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/Collateral
[HttpPost]
[ProducesResponseType(typeof(Collateral), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> Post([FromBody] Collateral collateral)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var result = await _collateralService.SaveAsync(collateral);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// PUT: api/Collateral/5
[HttpPut("{id}")]
[ProducesResponseType(typeof(Collateral), 200)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> Put(int id, [FromBody] Collateral collateral)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id <= 0 || id != collateral.CollateralId)
{
return BadRequest("Invalid ID");
}
var result = await _collateralService.UpdateAsync(collateral);
return Ok(result);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
}
}
@@ -0,0 +1,252 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using LCU.API.Collections.RepoApi.Configuration;
using LCU.API.Collections.RepoApi.Exceptions;
using LCU.API.Collections.RepoApi.Extensions;
using LCU.API.Collections.RepoApi.Services;
using LCU.Shared.Collections.RepoApiLib.Exceptions;
using LCU.Shared.Collections.RepoApiLib.Models;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement.Mvc;
using Serilog;
namespace LCU.API.Collections.RepoApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
[FeatureGate(FeatureFlags.EnableControllers)]
public class EventAlertController : RepoControllerBase
{
private readonly IEventAlertService _eventAlertService;
private readonly IRepoAccountService _repoAccountService;
public EventAlertController(
IEventAlertService eventAlertService,
IRepoAccountService repoAccountService,
IHttpContextAccessor httpContextAccessor) : base(httpContextAccessor)
{
_eventAlertService = eventAlertService;
_repoAccountService = repoAccountService;
}
// GET: api/EventAlert
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<EventAlert>), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> Get(bool unreadOnly = false, bool actionNeeded = false)
{
try
{
if (unreadOnly)
{
var unread = await _eventAlertService.ListUnread();
return Ok(unread);
}
if (actionNeeded)
{
var needed = await _eventAlertService.ListActionNeeded();
return Ok(needed);
}
var results = await _eventAlertService.ListAll();
return Ok(results);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {this.GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/EventAlert/5
[HttpGet("{id}", Name = "GetEventAlertById")]
[ProducesResponseType(typeof(EventAlert), 200)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetById(int id)
{
try
{
if (id <= 0)
{
return BadRequest("Invalid ID");
}
var result = await _eventAlertService.Get(id);
return Ok(result);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {this.GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/EventAlert/
[HttpGet]
[Route("[action]")]
[ProducesResponseType(typeof(EventAlert), 200)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetWithinDateRange(DateTime start, DateTime end)
{
try
{
if (start == null || end == null || end < start)
{
return BadRequest("Invalid dates");
}
var result = await _eventAlertService.ListWithinDateRange(start, end);
return Ok(result);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {this.GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/EventAlert
[HttpPost]
[ProducesResponseType(typeof(EventAlert), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> Post([FromBody] EventAlert eventAlert)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var result = await _eventAlertService.SaveAsync(eventAlert);
return Ok(result);
}
catch (DuplicateResourceException)
{
return StatusCode(StatusCodes.Status409Conflict, "A Duplicate Event Alert already exists.");
}
catch (UnwantedStatusException)
{
return StatusCode(StatusCodes.Status409Conflict, $"Status of Repo: Reposession ID: {eventAlert.RepoId} is Cancelled or Redeemed. Event Alert not created.");
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {this.GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/EventAlert/ByAuctionAssignmentId/:AuctionAssignmentId
[HttpPost("ByAuctionAssignmentId/{auctionAssignmentId}")]
[ProducesResponseType(typeof(EventAlert), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> CreateByAuctionAssignmentId(int auctionAssignmentId,
[FromBody] EventAlert eventAlert)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var repo = await _repoAccountService.FindByAuctionAssignmentId(auctionAssignmentId);
if (repo == null)
{
return BadRequest("Repo Account Not Found For Auction Assignment");
}
eventAlert.RepoId = repo.RepoId;
eventAlert.AccountNo = repo.AccountNo;
var result = await _eventAlertService.SaveAsync(eventAlert);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {this.GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// PUT: api/EventAlert/5
[HttpPut("{id}")]
[ProducesResponseType(typeof(EventAlert), 200)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public async Task<IActionResult> Put(int id, [FromBody] EventAlert eventAlert)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
if (id <= 0 || id != eventAlert.Id)
{
return BadRequest("Invalid ID");
}
var currentUser = CreateUserFromAuth();
var result = await _eventAlertService.UpdateAsync(eventAlert, currentUser);
return Ok(result);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {this.GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// DELETE: api/ApiWithActions/5
[HttpDelete("{id}")]
[ProducesResponseType(typeof(EventAlert), 200)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> Delete(int id)
{
try
{
if (id <= 0)
{
return BadRequest("Invalid ID");
}
var result = await _eventAlertService.DeleteAsync(id);
return Ok(result);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {this.GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
}
}
@@ -0,0 +1,264 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using LCU.API.Collections.RepoApi.Configuration;
using LCU.API.Collections.RepoApi.Models.Responses;
using LCU.API.Collections.RepoApi.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement.Mvc;
using Serilog;
namespace LCU.API.Collections.RepoApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
[FeatureGate(FeatureFlags.EnableControllers)]
public class FactoidController : RepoControllerBase
{
private readonly IFactoidService _factoidService;
public FactoidController(IHttpContextAccessor httpContextAccessor,
IFactoidService factoidService) :
base(httpContextAccessor)
{
_factoidService = factoidService;
}
// GET: api/Factoid
[HttpGet(Name = "GetAllFactoids")]
[ProducesResponseType(typeof(IEnumerable<FactoidResponse>), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetAllFactoids()
{
try
{
var highDate = DateTime.Today;
var lowDate = highDate.Subtract(TimeSpan.FromDays(30));
var factoidResponse = new List<FactoidResponse>()
{
await _factoidService.GetAllAccountsO4R(),
await _factoidService.GetAvgDaysO4RToREPO(lowDate, highDate),
await _factoidService.GetCurrentInventory(),
await _factoidService.GetPercentFloorPrice(lowDate, highDate),
await _factoidService.GetCurrentlyOnHold(),
await _factoidService.GetRecentlyRedeemed(lowDate, highDate),
await _factoidService.GetRecentlyRepoed(lowDate, highDate),
await _factoidService.GetGetAvgDaysDelqToO4R(lowDate, highDate),
await _factoidService.GetGetAvgDaysRepoToSold(lowDate, highDate),
await _factoidService.GetRecentlySold(lowDate, highDate)
};
return Ok(factoidResponse);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/factoid/CurrentInventory
[HttpGet("CurrentInventory", Name = "GetCurrentInventory")]
[ProducesResponseType(typeof(FactoidResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetCurrentInventory()
{
try
{
var factoidResponse = await _factoidService.GetCurrentInventory();
return Ok(factoidResponse);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at Factoid: {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/factoid/AccountsO4R
[HttpGet("AccountsO4R", Name = "GetAllAccountsInO4R")]
[ProducesResponseType(typeof(FactoidResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetAllAccountsO4R()
{
try
{
var factoidResponse = await _factoidService.GetAllAccountsO4R();
return Ok(factoidResponse);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at Factoid: {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/factoid/PercentFloor
[HttpGet("PercentFloor", Name = "GetPercentFloor")]
[ProducesResponseType(typeof(FactoidResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetPercentFloor()
{
try
{
var highDate = DateTime.Today;
var lowDate = highDate.Subtract(TimeSpan.FromDays(30));
var factoidResponse = await _factoidService.GetPercentFloorPrice(lowDate, highDate);
return Ok(factoidResponse);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at Factoid: {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/factoid/RecentlyRedeemed
[HttpGet("RecentlyRedeemed", Name = "GetRecentlyRedeemed")]
[ProducesResponseType(typeof(FactoidResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetRecentlyRedeemed()
{
try
{
var highDate = DateTime.Today;
var lowDate = highDate.Subtract(TimeSpan.FromDays(30));
var factoidResponse = await _factoidService.GetRecentlyRedeemed(lowDate, highDate);
return Ok(factoidResponse);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at Factoid: {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/factoid/AvgDaysO4RToREPO
[HttpGet("AvgDaysO4RToREPO", Name = "GetAvgDaysO4RToRepo")]
[ProducesResponseType(typeof(FactoidResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetAvgDaysO4RToRepo()
{
try
{
var highDate = DateTime.Today;
var lowDate = highDate.Subtract(TimeSpan.FromDays(30));
var factoidResponse = await _factoidService.GetAvgDaysO4RToREPO(lowDate, highDate);
return Ok(factoidResponse);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at Factoid: {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/factoid/AvgDaysDelqToO4R
[HttpGet("AvgDaysDelqToO4R", Name = "GetAvgDaysDelqToO4R")]
[ProducesResponseType(typeof(FactoidResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetAvgDaysDelqToO4R()
{
try
{
var highDate = DateTime.Today;
var lowDate = highDate.Subtract(TimeSpan.FromDays(30));
var factoidResponse = await _factoidService.GetGetAvgDaysDelqToO4R(lowDate, highDate);
return Ok(factoidResponse);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at Factoid: {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/factoid/AvgDaysRepoToSold
[HttpGet("AvgDaysRepoToSold", Name = "GetAvgDaysRepoToSold")]
[ProducesResponseType(typeof(FactoidResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetAvgDaysRepoToSold()
{
try
{
var highDate = DateTime.Today;
var lowDate = highDate.Subtract(TimeSpan.FromDays(30));
var factoidResponse = await _factoidService.GetGetAvgDaysRepoToSold(lowDate, highDate);
return Ok(factoidResponse);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at Factoid: {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/factoid/CurrentOnHold
[HttpGet("CurrentOnHold", Name = "GetCurrentOnHold")]
[ProducesResponseType(typeof(FactoidResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetCurrentOnHold()
{
try
{
var factoidResponse = await _factoidService.GetCurrentlyOnHold();
return Ok(factoidResponse);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at Factoid: {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/factoid/RecentlyRepoed
[HttpGet("RecentlyRepoed", Name = "GetRecentlyRepoed")]
[ProducesResponseType(typeof(FactoidResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetRecentlyRepoed()
{
try
{
var highDate = DateTime.Today;
var lowDate = highDate.Subtract(TimeSpan.FromDays(30));
var factoidResponse = await _factoidService.GetRecentlyRepoed(lowDate, highDate);
return Ok(factoidResponse);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at Factoid: {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/factoid/RecentlySold
[HttpGet("RecentlySold", Name = "GetRecentlySold")]
[ProducesResponseType(typeof(FactoidResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetRecentlySold()
{
try
{
var highDate = DateTime.Today;
var lowDate = highDate.Subtract(TimeSpan.FromDays(30));
var factoidResponse = await _factoidService.GetRecentlySold(lowDate, highDate);
return Ok(factoidResponse);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at Factoid: {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
}
}
@@ -0,0 +1,48 @@
using System;
using LCU.API.Collections.RepoApi.Configuration;
using LCU.Shared.Collections.RepoApiLib.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement.Mvc;
using Serilog;
namespace LCU.API.Collections.RepoApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
[FeatureGate(FeatureFlags.EnableControllers)]
public class LoggingController : RepoControllerBase
{
public LoggingController(IHttpContextAccessor httpContextAccessor) : base(httpContextAccessor)
{
}
/// <summary>
/// Use to send something to the logger from the client
/// </summary>
/// <remarks>
/// Logs a message from a client.
/// Cookies: None required or set.
/// Security: Must have a valid JWT token retrieved from Azure AD, with ADMIN role.
/// </remarks>
// Post: api/Logging/Error
[HttpPost("Error")]
[ProducesResponseType(200)]
[ProducesResponseType(400)]
[ProducesResponseType(403)]
[ProducesResponseType(500)]
public IActionResult Post([FromBody] ClientError error)
{
try
{
Log.Error($"Client Exception: <{ error.Name}> : {error.Message} => {error.StackTrace}");
return Ok();
}
catch (Exception e)
{
Log.Fatal(e,"Failed to log client error. Exception Thrown.");
return StatusCode((int) System.Net.HttpStatusCode.InternalServerError);
}
}
}
}
@@ -0,0 +1,60 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using LCU.API.Collections.RepoApi.Configuration;
using LCU.API.Collections.RepoApi.HostedServices;
using LCU.API.Collections.RepoApi.Services;
using LCU.Shared.Collections.RepoApiLib.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement.Mvc;
using Serilog;
namespace LCU.API.Collections.RepoApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
[FeatureGate(FeatureFlags.EnableControllers)]
public class ProcessingReceiptController : RepoControllerBase
{
private readonly IProcessingReceiptService _receiptService;
public ProcessingReceiptController(IProcessingReceiptService receiptService,
IHttpContextAccessor httpContextAccessor):
base(httpContextAccessor)
{
_receiptService = receiptService;
}
//GET: api/ProcessingReceipt/AccountNo/5
[HttpGet("AccountNo/{accountNo}")]
[ProducesResponseType(typeof(ReadOnlyCollection<ProcessingReceipt>),200)]
[ProducesResponseType(typeof(string),400)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetByAccountNo(string accountNo)
{
try
{
if (string.IsNullOrWhiteSpace((accountNo)))
{
return BadRequest("Invalid ID");
}
var results = await _receiptService.GetReceiptsByAccountNoAsync(accountNo);
if (results.Any())
{
return Ok(results);
}
return NotFound("No receipts found for for this account");
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
}
}
@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using LCU.API.Collections.RepoApi.Configuration;
using LCU.API.Collections.RepoApi.Extensions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Serilog;
using LCU.API.Collections.RepoApi.HostedServices;
using LCU.API.Collections.RepoApi.Models.Requests;
using LCU.Shared.Collections.RepoApiLib.Models.Producer;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.FeatureManagement.Mvc;
namespace LCU.API.Collections.RepoApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
[FeatureGate(FeatureFlags.EnableControllers)]
public class QueueController : RepoControllerBase
{
private readonly IRepoQueueProducerService _repoQueueProducer;
public QueueController(IRepoQueueProducerService producerService, IHttpContextAccessor httpContextAccessor) :
base(httpContextAccessor)
{
_repoQueueProducer = producerService;
}
[HttpPost]
[ProducesResponseType(200)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public IActionResult PutSingleAccountOnQueue([FromBody] AccountQueueRequest queueRequest)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var currentUser = CreateUserFromAuth();
var request = new ProcessingItem()
{
AccountNo = queueRequest.AccountNo,
CaseId = queueRequest.CaseId,
QueuedBy = currentUser.Email
};
_repoQueueProducer.SendRequestToQueue(request);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode((int) System.Net.HttpStatusCode.InternalServerError,
$"A server error occured, and your request could not be complete");
}
return Ok();
}
}
}
@@ -0,0 +1,141 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using LCU.API.Collections.RepoApi.Configuration;
using LCU.API.Collections.RepoApi.Exceptions;
using LCU.API.Collections.RepoApi.Extensions;
using LCU.API.Collections.RepoApi.Services;
using LCU.Shared.Collections.RepoApiLib.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement.Mvc;
using Serilog;
namespace LCU.API.Collections.RepoApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
[FeatureGate(FeatureFlags.EnableControllers)]
public class RefundStatusController : RepoControllerBase
{
private IRefundStatusService _refundStatusService;
public RefundStatusController(
IRefundStatusService refundStatusService,
IHttpContextAccessor httpContextAccessor) :
base(httpContextAccessor)
{
_refundStatusService = refundStatusService;
}
// GET: api/RefundStatus
[HttpGet(Name = "GetAllRefundStatus")]
[ProducesResponseType(typeof(IEnumerable<RefundStatus>), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> Get()
{
try
{
var result = await _refundStatusService.ListAllAsync();
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/RefundStatus/5
[HttpGet("{repoId}", Name = "GetRefundStatusById")]
[ProducesResponseType(typeof(RefundStatus), 200)]
[ProducesResponseType(typeof(string), 404)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetById(int repoId)
{
try
{
if (repoId <= 0)
{
return BadRequest("Invalid RepoId");
}
var refundStatus = await _refundStatusService.GetAsync(repoId);
return Ok(refundStatus);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPost]
[ProducesResponseType(typeof(RefundStatus), 200)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> Post([FromBody] RefundStatus refundStatus)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
if (refundStatus.RepoId <= 0)
{
return BadRequest("Invalid ID");
}
var newRefundStatus = await _refundStatusService.SaveAsync(refundStatus);
return Ok(newRefundStatus);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPut("{repoId}")]
[ProducesResponseType(typeof(RefundStatus), 200)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> Put(int repoId, [FromBody] RefundStatus refundStatus)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
if (repoId <= 0 || repoId != refundStatus.RepoId)
{
return BadRequest("Invalid ID");
}
var newRefundStatus = await _refundStatusService.UpdateAsync(refundStatus);
return Ok(newRefundStatus);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
}
}
@@ -0,0 +1,343 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using LCU.API.Collections.RepoApi.Configuration;
using LCU.API.Collections.RepoApi.Exceptions;
using LCU.API.Collections.RepoApi.Extensions;
using LCU.API.Collections.RepoApi.Models;
using LCU.API.Collections.RepoApi.Models.Filters;
using LCU.API.Collections.RepoApi.Models.Requests;
using LCU.API.Collections.RepoApi.Models.Responses;
using LCU.API.Collections.RepoApi.Services;
using LCU.Shared.Collections.RepoApiLib.Exceptions;
using LCU.Shared.Collections.RepoApiLib.Models;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement.Mvc;
using Serilog;
namespace LCU.API.Collections.RepoApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
[FeatureGate(FeatureFlags.EnableControllers)]
public class RepoAccountController : RepoControllerBase
{
private readonly IRepoAccountService _repoAccountService;
private readonly IRepoLetterService _repoLetterService;
private readonly IRepoStatusChangeService _repoStatusChangeService;
public RepoAccountController(
IRepoAccountService repoAccountService,
IRepoLetterService repoLetterService,
IRepoStatusChangeService repoStatusChangeService,
IHttpContextAccessor httpContextAccessor) : base(httpContextAccessor)
{
_repoAccountService = repoAccountService;
_repoLetterService = repoLetterService;
_repoStatusChangeService = repoStatusChangeService;
}
// GET: api/RepoAccount
[HttpGet(Name = "GetAllRepoAccounts")]
[ProducesResponseType(typeof(PagedResponse<RepoAccount>), 200)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> Get([FromQuery] PageFilter filter)
{
try
{
var searchResult = await _repoAccountService.ListAll(filter);
var result = new PagedResponse<RepoAccount>(searchResult, filter, this.Url, Request);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/RepoAccount/5
[HttpGet("{id}", Name = "GetRepoAccountById")]
[ProducesResponseType(typeof(RepoAccount), 200)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetById(int id)
{
try
{
if (id <= 0)
{
return BadRequest("Invalid ID");
}
var result = await _repoAccountService.Get(id);
return Ok(result);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/RepoAccount/5/statusChanges
[HttpGet("{id}/statusChanges", Name = "GetRepoAccountStatusChanges")]
[ProducesResponseType(typeof(RepoStatusChange), 200)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetStatusChangesByRepoId(int id)
{
try
{
if (id <= 0)
{
return BadRequest("Invalid ID");
}
var result = await _repoStatusChangeService.ListByAccount(id);
return Ok(result);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/RepoAccount/5/repoLetters
[HttpGet("{id}/repoLetters", Name = "GetLettersByRepoId")]
[ProducesResponseType(typeof(RepoLetter), 200)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetLettersByRepoId(int id)
{
try
{
if (id <= 0)
{
return BadRequest("Invalid ID");
}
var result = await _repoLetterService.ListByAccountIdAsync(id);
return Ok(result);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/RepoAccount/Search/searchPhrase
[HttpGet("Search/{searchPhrase}", Name = "SearchRepoAccounts")]
[ProducesResponseType(typeof(RepoAccount), 200)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> SearchRepoAccounts(string searchPhrase)
{
var maxResults = 100;
try
{
if (string.IsNullOrWhiteSpace(searchPhrase))
{
return BadRequest("Search Phrase Cannot Be Empty");
}
if (searchPhrase.Trim().Length < 3)
{
return BadRequest("Search Phrase Must Be At Least Three (3) Characters Long");
}
var result = await _repoAccountService.SearchRepoAccounts(searchPhrase, maxResults);
return Ok(result);
}
catch (MaxItemException ex)
{
return StatusCode(StatusCodes.Status413PayloadTooLarge, $"Too Many Search Results. (Max: {maxResults}) (Found: {ex.ItemCount})");
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/RepoAccount/AccountNo/5
[HttpGet("AccountNo/{accountNo}", Name = "GetRepoAccountByAccountNo")]
[ProducesResponseType(typeof(RepoAccount), 200)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetByAccountNo(string accountNo)
{
try
{
if (string.IsNullOrWhiteSpace(accountNo))
{
return BadRequest("Invalid ID");
}
var result = await _repoAccountService.GetByAccountNo(accountNo);
return Ok(result);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/RepoAccount
[HttpPost]
[ProducesResponseType(typeof(RepoAccount), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> Post([FromBody] RepoAccount repoAccount)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var result = await _repoAccountService.SaveAsync(repoAccount);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// PUT: api/RepoAccount/5
[HttpPut("{id}")]
[ProducesResponseType(typeof(RepoAccount), 200)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public async Task<IActionResult> Put(int id, [FromBody] RepoAccount repoAccount)
{
try
{
if (id <= 0 || id != repoAccount.RepoId)
{
return BadRequest("Invalid ID");
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var currentUser = CreateUserFromAuth();
var result = await _repoAccountService.UpdateAsync(repoAccount, currentUser);
return Ok(result);
}
catch (MaxItemException mie)
{
return BadRequest(mie.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// PUT: api/RepoAccount/Insecure/5
[HttpPut("Insecure/{id}", Name = "Update Account Insecure Batch")]
[ProducesResponseType(typeof(RepoAccount), 200)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> PutBatch(int id, [FromBody] RepoAccount repoAccount)
{
try
{
if (id <= 0 || id != repoAccount.RepoId)
{
return BadRequest("Invalid ID");
}
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
var currentUser = new AppUser()
{
FirstName = "System",
LastName = "User"
};
var result = await _repoAccountService.UpdateAsync(repoAccount, currentUser);
return Ok(result);
}
catch (MaxItemException mie)
{
return BadRequest(mie.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/Filter
[HttpPost("Filter")]
[ProducesResponseType(typeof(PagedResponse<RepoAccount>), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> Filter([FromQuery] PageFilter pageFilter, [FromBody] RepoAccountPostFilter repoAccountFilter)
{
try
{
var searchResult = await _repoAccountService.ListAll(pageFilter, repoAccountFilter);
var result = new PagedResponse<RepoAccount>(searchResult, pageFilter, this.Url, Request, "Filter");
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpDelete("Delete/{AccountId}")]
[ProducesResponseType(200)]
[ProducesResponseType(500)]
[ProducesResponseType(400)]
public async Task<IActionResult> DeleteAccountById([FromRoute] int accountId)
{
try
{
await _repoAccountService.DeleteAsync(accountId);
}catch(Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
return Ok($"Delete of Account with Id: {accountId} Successful");
}
}
}
@@ -0,0 +1,140 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using LCU.API.Collections.RepoApi.Configuration;
using LCU.API.Collections.RepoApi.Exceptions;
using LCU.API.Collections.RepoApi.Extensions;
using LCU.API.Collections.RepoApi.Services;
using LCU.Shared.Collections.RepoApiLib.Models;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement.Mvc;
using Serilog;
namespace LCU.API.Collections.RepoApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
[FeatureGate(FeatureFlags.EnableControllers)]
public class RepoCompanyController : RepoControllerBase
{
private readonly IRepoCompanyService _repoCompanyService;
public RepoCompanyController(
IRepoCompanyService repoCompanyService,
IHttpContextAccessor httpContextAccessor) :
base(httpContextAccessor)
{
_repoCompanyService = repoCompanyService;
}
// GET: api/RepoCompany
[ProducesResponseType(typeof(IEnumerable<RepoCompany>), 200)]
[ProducesResponseType(500)]
[HttpGet]
public async Task<IActionResult> Get()
{
try
{
var repoCompanies = await _repoCompanyService.ListAll();
return Ok(repoCompanies);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/RepoCompany/5
[HttpGet("{id}", Name = "GetRepoCompanyById")]
[ProducesResponseType(typeof(RepoCompany), 200)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetById(int id)
{
try
{
if (id <= 0)
{
return BadRequest("Invalid ID");
}
var repoCompany = await _repoCompanyService.Get(id);
return Ok(repoCompany);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/RepoCompany
[HttpPost]
[ProducesResponseType(typeof(RepoCompany), 200)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public async Task<IActionResult> Post([FromBody] RepoCompany repoCompany)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var newRepoCompany = await _repoCompanyService.Save(repoCompany);
return Ok(newRepoCompany);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// PUT: api/RepoCompany/5
[HttpPut("{id}")]
[ProducesResponseType(typeof(RepoCompany), 200)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public async Task<IActionResult> Put(int id, [FromBody] RepoCompany repoCompany)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
if (id <= 0 || id != repoCompany.ID)
{
return BadRequest("Invalid ID");
}
var currentUser = CreateUserFromAuth();
var updatedRepoCompany = await _repoCompanyService.Update(repoCompany, currentUser);
return Ok(updatedRepoCompany);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
}
}
@@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using LCU.API.Collections.RepoApi.Configuration;
using LCU.API.Collections.RepoApi.Exceptions;
using LCU.API.Collections.RepoApi.Services;
using LCU.Shared.Collections.RepoApiLib.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement.Mvc;
using Serilog;
namespace LCU.API.Collections.RepoApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
[FeatureGate(FeatureFlags.EnableControllers)]
public class RepoCompanySelectionController : RepoControllerBase
{
private readonly IRepoCompanySelectionService _repoCompanySelectionService;
public RepoCompanySelectionController(
IRepoCompanySelectionService repoCompanySelectionService,
IHttpContextAccessor httpContextAccessor) :
base(httpContextAccessor)
{
_repoCompanySelectionService = repoCompanySelectionService;
}
// GET: api/RepoCompanySelection
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<RepoCompanySelection>), 200)]
[ProducesResponseType(500)]
public async Task<ActionResult> Get()
{
try
{
var repoCompanySelections = await _repoCompanySelectionService.ListAllAsync();
return Ok(repoCompanySelections);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/RepoCompanySelection/5
[HttpGet("{id}", Name = "GetRepoCompanySelectionById")]
[ProducesResponseType(typeof(RepoCompanySelection), 200)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(typeof(string), 404)]
[ProducesResponseType(500)]
public async Task<ActionResult> Get(int id)
{
try
{
if (id <= 0)
{
return BadRequest("Invalid ID");
}
var repoCompanySelection = await _repoCompanySelectionService.GetAsync(id);
return Ok(repoCompanySelection);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
}
}
@@ -0,0 +1,37 @@
using System.Linq;
using System.Runtime.CompilerServices;
using System.Security.Claims;
using LCU.API.Collections.RepoApi.Configuration;
using LCU.API.Collections.RepoApi.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement.Mvc;
namespace LCU.API.Collections.RepoApi.Controllers
{
public class RepoControllerBase : ControllerBase
{
protected readonly IHttpContextAccessor HttpContextAccessor;
protected RepoControllerBase(IHttpContextAccessor httpContextAccessor)
{
HttpContextAccessor = httpContextAccessor;
}
protected AppUser CreateUserFromAuth()
{
var userClaimsPrincipal = HttpContextAccessor.HttpContext.User;
return new AppUser
{
FirstName = userClaimsPrincipal.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.GivenName)?.Value,
LastName = userClaimsPrincipal.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.Surname)?.Value,
Email = userClaimsPrincipal.Claims.FirstOrDefault(claim => claim.Type == ClaimTypes.Upn)?.Value,
Roles = userClaimsPrincipal.Claims.Where((claim) => claim.Type == ClaimTypes.Role).Select((claim) => claim?.Value).ToArray(),
};
}
public static string GetInvokerAsyncMethodName([CallerMemberName] string methodName = null) => methodName;
}
}
@@ -0,0 +1,382 @@
using System;
using System.Threading.Tasks;
using LCU.API.Collections.RepoApi.Configuration;
using LCU.API.Collections.RepoApi.Models.Requests;
using LCU.API.Collections.RepoApi.Services.Reporting;
using LCU.Shared.Collections.RepoApiLib.Models.Reporting.Responses;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement.Mvc;
using Serilog;
namespace LCU.API.Collections.RepoApi.Controllers
{
[Route("api/[controller]")]
[FeatureGate(FeatureFlags.EnableControllers)]
[ApiController]
public class ReportingController : RepoControllerBase
{
private readonly ILandmarkReportingService _landmarkReportingService;
private readonly IAuctionReportingService _auctionReportingService;
private readonly IRepoCompanyReportingService _repoCompanyReportingService;
public ReportingController(
IHttpContextAccessor httpContextAccessor,
ILandmarkReportingService landmarkReportingService,
IAuctionReportingService auctionReportingService,
IRepoCompanyReportingService repoCompanyReportingService) :
base(httpContextAccessor)
{
_landmarkReportingService = landmarkReportingService;
_auctionReportingService = auctionReportingService;
_repoCompanyReportingService = repoCompanyReportingService;
}
// POST: api/Reporting/AuctionAssignedBalance
[HttpPost("AuctionAssignedBalance", Name = "AuctionAssignedBalance")]
[ProducesResponseType(typeof(ReportTableResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetAuctionAssignedBalance([FromBody] ReportingRequest reportingRequest)
{
try
{
var results = await _auctionReportingService.GetAssignedBalance(reportingRequest.LowDate, reportingRequest.HighDate);
return Ok(results);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/Reporting/AuctionCountOfAssignedCollateral
[HttpPost("AuctionCountOfAssignedCollateral", Name = "AuctionCountOfAssignedCollateral")]
[ProducesResponseType(typeof(ReportTableResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetAuctionCountOfAssignedCollateral([FromBody] ReportingRequest reportingRequest)
{
try
{
var results = await _auctionReportingService.GetAssignedCollateralCount(reportingRequest.LowDate, reportingRequest.HighDate);
return Ok(results);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/Reporting/AuctionSalePrice
[HttpPost("AuctionSalePrice", Name = "AuctionSalePrice")]
[ProducesResponseType(typeof(ReportTableResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetAuctionSalePrice([FromBody] ReportingRequest reportingRequest)
{
try
{
var results = await _auctionReportingService.GetSalePriceReport(reportingRequest.LowDate, reportingRequest.HighDate);
return Ok(results);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/Reporting/AuctionTotalFees
[HttpPost("AuctionTotalFees", Name = "AuctionTotalFees")]
[ProducesResponseType(typeof(ReportTableResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetAuctionTotalFees([FromBody] ReportingRequest reportingRequest)
{
try
{
var results = await _auctionReportingService.GetAuctionFeeReport(reportingRequest.LowDate, reportingRequest.HighDate);
return Ok(results);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/Reporting/AuctionAvgDaysToSold
[HttpPost("AuctionAvgDaysToSold", Name = "AuctionAvgDaysToSold")]
[ProducesResponseType(typeof(ReportTableResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetAuctionAvgDaysToSold([FromBody] ReportingRequest reportingRequest)
{
try
{
var result = await _auctionReportingService.GetAvgDaysAssignedToSold(reportingRequest.LowDate, reportingRequest.HighDate);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/Reporting/AuctionAvgNetOfFloor
[HttpPost("AuctionAvgNetOfFloor", Name = "AuctionAvgNetOfFloor")]
[ProducesResponseType(typeof(ReportTableResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetAuctionAvgNetOfFloor([FromBody] ReportingRequest reportingRequest)
{
try
{
var result = await _auctionReportingService.GetAvgNetOfFloor(reportingRequest.LowDate, reportingRequest.HighDate);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/Reporting/RepoCompanyCountOfAssignedCollateral
[HttpPost("RepoCompanyCountOfAssignedCollateral", Name = "RepoCompanyCountOfAssignedCollateral")]
[ProducesResponseType(typeof(ReportTableResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetRepoCompanyCountOfAssignedCollateral([FromBody] ReportingRequest reportingRequest)
{
try
{
var results = await _repoCompanyReportingService.GetAssignmentCountReport(reportingRequest.LowDate, reportingRequest.HighDate);
return Ok(results);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/Reporting/RepoCompanyAssignedBalance
[HttpPost("RepoCompanyAssignedBalance", Name = "RepoCompanyAssignedBalance")]
[ProducesResponseType(typeof(ReportTableResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetRepoCompanyAssignedBalance([FromBody] ReportingRequest reportingRequest)
{
try
{
var result = await _repoCompanyReportingService.GetAssignedBalance(reportingRequest.LowDate, reportingRequest.HighDate);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/Reporting/RepoCompanyRepoCount
[HttpPost("RepoCompanyRepoCount", Name = "RepoCompanyRepoCount")]
[ProducesResponseType(typeof(ReportTableResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetRepoCompanyRepoCount([FromBody] ReportingRequest reportingRequest)
{
try
{
var result = await _repoCompanyReportingService.GetRepoedCountReport(reportingRequest.LowDate, reportingRequest.HighDate);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/Reporting/RepoCompanyAvgDaysAssignedToRepo
[HttpPost("RepoCompanyAvgDaysAssignedToRepo", Name = "RepoCompanyAvgDaysAssignedToRepo")]
[ProducesResponseType(typeof(ReportTableResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetRepoCompanyAvgDaysAssignedToRepo([FromBody] ReportingRequest reportingRequest)
{
try
{
var result = await _repoCompanyReportingService.GetAvgDaysAssignedToRepoed(reportingRequest.LowDate, reportingRequest.HighDate);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/Reporting/AvgDaysDlqWhenAdded
[HttpPost("AvgDaysDlqWhenAdded", Name = "AvgDaysDlqWhenAdded")]
[ProducesResponseType(typeof(ReportTableResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetAvgDaysDlqWhenAdded([FromBody] ReportingRequest reportingRequest)
{
try
{
var result = await _landmarkReportingService.GetAvgDaysDelqWhenAddedReport(reportingRequest.LowDate, reportingRequest.HighDate);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/Reporting/AvgDaysRepoToSold
[HttpPost("AvgDaysRepoToSold", Name = "AvgDaysRepoToSold")]
[ProducesResponseType(typeof(ReportTableResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetAvgDaysRepoToSold([FromBody] ReportingRequest reportingRequest)
{
try
{
var result = await _landmarkReportingService.GetAvgDaysRepoToSoldReport(reportingRequest.LowDate, reportingRequest.HighDate);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/Reporting/CountOfSales
[HttpPost("CountOfSales", Name = "CountOfSales")]
[ProducesResponseType(typeof(ReportTableResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetCountOfSales([FromBody] ReportingRequest reportingRequest)
{
try
{
var result = await _landmarkReportingService.GetCountOfSalesReport(reportingRequest.LowDate, reportingRequest.HighDate);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/Reporting/AvgSurplusOrDeficiency
[HttpPost("AvgSurplusOrDeficiency", Name = "AvgSurplusOrDeficiency")]
[ProducesResponseType(typeof(ReportTableResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetAvgSurplusOrDeficiency([FromBody] ReportingRequest reportingRequest)
{
try
{
var result = await _landmarkReportingService.GetAvgSurplusReport(reportingRequest.LowDate, reportingRequest.HighDate);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/Reporting/CountOfRedemptions
[HttpPost("CountOfRedemptions", Name = "CountOfRedemptions")]
[ProducesResponseType(typeof(ReportTableResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetCountOfRedemptions([FromBody] ReportingRequest reportingRequest)
{
try
{
var result = await _landmarkReportingService.GetRedemptionCountReport(reportingRequest.LowDate, reportingRequest.HighDate);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/Reporting/TotalRedemptionBalance
[HttpPost("TotalRedemptionBalance", Name = "TotalRedemptionBalance")]
[ProducesResponseType(typeof(ReportTableResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetTotalRedemptionBalance([FromBody] ReportingRequest reportingRequest)
{
try
{
var result = await _landmarkReportingService.GetRedemptionBalanceReport(reportingRequest.LowDate, reportingRequest.HighDate);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/Reporting/LoanBalanceRepoedCollateral
[HttpPost("LoanBalanceInventory", Name = "LoanBalanceInventory")]
[ProducesResponseType(typeof(ReportTableResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetLoanBalanceInventory([FromBody] ReportingRequest reportingRequest)
{
try
{
var result = await _landmarkReportingService.GetRepoBalanceReport(reportingRequest.LowDate, reportingRequest.HighDate);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/Reporting/IsActiveAccount
[HttpPost("IsActiveAccount", Name = "IsActiveAccount")]
[ProducesResponseType(typeof(ReportTableResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetIsActiveAccount([FromBody] ReportingRequest reportingRequest)
{
try
{
var results =
await _landmarkReportingService.GetIsActiveAccountReport(reportingRequest.LowDate,
reportingRequest.HighDate);
return Ok(results);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/Reporting/IsClosedAccount
[HttpPost("IsClosedAccount", Name = "IsClosedAccount")]
[ProducesResponseType(typeof(ReportTableResponse), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetIsClosedAccount([FromBody] ReportingRequest reportingRequest)
{
try
{
var results =
await _landmarkReportingService.GetIsClosedAccountReport(reportingRequest.LowDate,
reportingRequest.HighDate);
return Ok(results);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
}
}
@@ -0,0 +1,163 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using LCU.API.Collections.RepoApi.Configuration;
using LCU.API.Collections.RepoApi.Exceptions;
using LCU.API.Collections.RepoApi.Extensions;
using LCU.API.Collections.RepoApi.Services;
using LCU.Shared.Collections.RepoApiLib.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement.Mvc;
using Serilog;
namespace LCU.API.Collections.RepoApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
[FeatureGate(FeatureFlags.EnableControllers)]
public class SavedLocationController : RepoControllerBase
{
private readonly ISavedLocationService _savedLocationService;
public SavedLocationController(
ISavedLocationService savedLocationService,
IHttpContextAccessor httpContextAccessor) :
base(httpContextAccessor)
{
_savedLocationService = savedLocationService;
}
// GET: api/SavedLocation
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<SavedLocation>), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> Get()
{
try
{
var savedLocations = await _savedLocationService.ListAllAsync();
return Ok(savedLocations);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/SavedLocation/5
[HttpGet("{id}", Name = "GetSavedLocationById")]
[ProducesResponseType(typeof(SavedLocation), 200)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetById(int id)
{
try
{
if (id <= 0)
{
return BadRequest("Invalid ID");
}
var savedLocation = await _savedLocationService.GetAsync(id);
return Ok(savedLocation);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/SavedLocation
[HttpPost]
[ProducesResponseType(typeof(SavedLocation), 200)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> Post([FromBody] SavedLocation savedLocation)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var newSavedLocation = await _savedLocationService.SaveAsync(savedLocation);
return Ok(newSavedLocation);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// PUT: api/SavedLocation/5
[HttpPut("{id}")]
[ProducesResponseType(typeof(SavedLocation), 200)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> Put(int id, [FromBody] SavedLocation savedLocation)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
if (id <= 0 || id != savedLocation.ID)
{
return BadRequest("Invalid ID");
}
var updatedSavedLocation = await _savedLocationService.UpdateAsync(savedLocation);
return Ok(updatedSavedLocation);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// DELETE: api/ApiWithActions/5
[HttpDelete("{id}")]
[ProducesResponseType(typeof(SavedLocation), 200)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> Delete(int id)
{
try
{
if (id <= 0)
{
return BadRequest("Invalid ID");
}
var result = await _savedLocationService.DeleteAsync(id);
return Ok(result);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
}
}
@@ -0,0 +1,531 @@
using System;
using System.Threading.Tasks;
using LCU.Shared.Collections.RepoApiLib.Models.Settings;
using LCU.API.Collections.RepoApi.Services;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using LCU.API.Collections.RepoApi.Configuration;
using LCU.API.Collections.RepoApi.Extensions;
using Microsoft.FeatureManagement.Mvc;
using Serilog;
namespace LCU.API.Collections.RepoApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
[FeatureGate(FeatureFlags.EnableControllers)]
public class SettingsController : RepoControllerBase
{
private readonly ISettingsService _settingsService;
public SettingsController(
ISettingsService settingsService,
IHttpContextAccessor httpContextAccessor) : base(httpContextAccessor)
{
_settingsService = settingsService;
}
// GET: api/Settings
[HttpGet]
[ProducesResponseType(typeof(Settings), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> Get()
{
try
{
var results = await _settingsService.ListAllSettingsAsync();
return Ok(results);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// PUT: api/Settings/[method]
[HttpPost]
[Route("[action]")]
[ProducesResponseType(typeof(AccountType), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> PutAccountType([FromBody] AccountType accountType)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var result = await _settingsService.UpdateAsync(accountType);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPost]
[Route("[action]")]
[ProducesResponseType(typeof(AllowedStatusChange), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> PutAllowedStatusChange([FromBody] AllowedStatusChange statusChange)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var result = await _settingsService.UpdateAsync(statusChange);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPost]
[Route("[action]")]
[ProducesResponseType(typeof(AllowedStatusChange), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> PutBodyStyle([FromBody] BodyStyle bodyStyle)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var result = await _settingsService.UpdateAsync(bodyStyle);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPost]
[Route("[action]")]
[ProducesResponseType(typeof(AllowedStatusChange), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> PutCleaningLevel([FromBody] CleaningLevel cleaningLevel)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var result = await _settingsService.UpdateAsync(cleaningLevel);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPost]
[Route("[action]")]
[ProducesResponseType(typeof(AllowedStatusChange), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> PutCollateralType([FromBody] CollateralType collateralType)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var result = await _settingsService.UpdateAsync(collateralType);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPost]
[Route("[action]")]
[ProducesResponseType(typeof(AllowedStatusChange), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> PutCollateralTypeToVehicleType(
[FromBody] CollateralTypeToVehicleType collateralToVehicle)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var result = await _settingsService.UpdateAsync(collateralToVehicle);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPost]
[Route("[action]")]
[ProducesResponseType(typeof(AllowedStatusChange), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> PutHoldStatus([FromBody] HoldStatus holdStatus)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var result = await _settingsService.UpdateAsync(holdStatus);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPost]
[Route("[action]")]
[ProducesResponseType(typeof(AllowedStatusChange), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> PutLoanCategory([FromBody] LoanCategory loanCategory)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var result = await _settingsService.UpdateAsync(loanCategory);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPost]
[Route("[action]")]
[ProducesResponseType(typeof(AllowedStatusChange), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> PutLocationType([FromBody] LocationType locationType)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var result = await _settingsService.UpdateAsync(locationType);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPost]
[Route("[action]")]
[ProducesResponseType(typeof(AllowedStatusChange), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> PutVehicleType([FromBody] VehicleType vehicleType)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var result = await _settingsService.UpdateAsync(vehicleType);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// POST: api/Settings/[method]
[HttpPost]
[Route("[action]")]
[ProducesResponseType(typeof(AccountType), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> PostAccountType([FromBody] AccountType accountType)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var result = await _settingsService.SaveAsync(accountType);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPost]
[Route("[action]")]
[ProducesResponseType(typeof(AccountType), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> PostAllowedStatusChange([FromBody] AllowedStatusChange statusChange)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var result = await _settingsService.SaveAsync(statusChange);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPost]
[Route("[action]")]
[ProducesResponseType(typeof(AccountType), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> PostBodyStyle([FromBody] BodyStyle bodyStyle)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var result = await _settingsService.SaveAsync(bodyStyle);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPost]
[Route("[action]")]
[ProducesResponseType(typeof(AccountType), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> PostCleaningLevel([FromBody] CleaningLevel cleaningLevel)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var result = await _settingsService.SaveAsync(cleaningLevel);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPost]
[Route("[action]")]
[ProducesResponseType(typeof(AccountType), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> PostCollateralType([FromBody] CollateralType collateralType)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var result = await _settingsService.SaveAsync(collateralType);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPost]
[Route("[action]")]
[ProducesResponseType(typeof(AccountType), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> PostCollateralTypeToVehicleType(
[FromBody] CollateralTypeToVehicleType collateralToVehicle)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var result = await _settingsService.SaveAsync(collateralToVehicle);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPost]
[Route("[action]")]
[ProducesResponseType(typeof(AccountType), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> PostHoldStatus([FromBody] HoldStatus holdStatus)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var result = await _settingsService.SaveAsync(holdStatus);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPost]
[Route("[action]")]
[ProducesResponseType(typeof(AccountType), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> PostLoanCategory([FromBody] LoanCategory loanCategory)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var result = await _settingsService.SaveAsync(loanCategory);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPost]
[Route("[action]")]
[ProducesResponseType(typeof(AccountType), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> PostLocationType([FromBody] LocationType locationType)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var result = await _settingsService.SaveAsync(locationType);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPost]
[Route("[action]")]
[ProducesResponseType(typeof(AccountType), 201)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> PostVehicleType([FromBody] VehicleType vehicleType)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var result = await _settingsService.SaveAsync(vehicleType);
return Ok(result);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
}
}
@@ -0,0 +1,36 @@
using System;
using LCU.Shared.Collections.RepoApiLib.Models;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Serilog;
namespace LCU.API.Collections.RepoApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class StatusController : RepoControllerBase
{
private readonly IEnvironmentView _environmentView;
public StatusController(IHttpContextAccessor httpContextAccessor, IEnvironmentView environmentView) : base(httpContextAccessor)
{
_environmentView = environmentView;
}
[HttpGet()]
[ProducesResponseType(200)]
[ProducesResponseType(500)]
public IActionResult Get()
{
try
{
return Ok(_environmentView);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode((int) System.Net.HttpStatusCode.InternalServerError);
}
}
}
}
@@ -0,0 +1,119 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using LCU.API.Collections.RepoApi.Configuration;
using LCU.API.Collections.RepoApi.Exceptions;
using LCU.API.Collections.RepoApi.Extensions;
using LCU.API.Collections.RepoApi.Models.Requests;
using LCU.API.Collections.RepoApi.Models.Responses;
using LCU.API.Collections.RepoApi.Services;
using LCU.Shared.Collections.RepoApiLib.Models;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.FeatureManagement.Mvc;
using Serilog;
namespace LCU.API.Collections.RepoApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
[FeatureGate(FeatureFlags.EnableControllers)]
public class VehicleFloorPricingParametersController : RepoControllerBase
{
private readonly IVehicleFloorPricingParametersService _parametersService;
public VehicleFloorPricingParametersController(
IVehicleFloorPricingParametersService parametersService,
IHttpContextAccessor httpContextAccessor) : base(httpContextAccessor)
{
_parametersService = parametersService;
}
// GET: api/VehicleFloorPricingParameters
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<VehicleFloorPricingParameters>), 200)]
[ProducesResponseType(500)]
public async Task<IActionResult> Get()
{
try
{
var results = await _parametersService.ListAll();
return Ok(results);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
// GET: api/VehicleFloorPricingParameters/5
[HttpGet("{id}", Name = "GetVehicleFloorPricingParametersById")]
[ProducesResponseType(typeof(VehicleFloorPricingParameters), 200)]
[ProducesResponseType(typeof(string), 400)]
[ProducesResponseType(500)]
public async Task<IActionResult> GetById(int id)
{
try
{
if (id <= 0)
{
return BadRequest("Invalid ID");
}
var result = await _parametersService.Get(id);
return Ok(result);
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
[HttpPut()]
[ProducesResponseType(typeof(AuctionHouse), 200)]
[ProducesResponseType(typeof(List<string>), 400)]
[ProducesResponseType(500)]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme, Roles = "Admin")]
public async Task<IActionResult> Put([FromBody] VehicleFloorPricingParametersPutRequest request)
{
try
{
if (!ModelState.IsValid)
{
return BadRequest(ModelState.GetErrorMessages());
}
var updatedParams = await UpdateVehiceFloorPricingParameters(request.vehicleFloorPricingParameters);
return Ok(new VehicleFloorPricingParametersPutResponse { vehicleFloorPricingParameters = updatedParams });
}
catch (NotFoundException nfe)
{
return NotFound(nfe.Message);
}
catch (Exception ex)
{
Log.Error(ex, $"Failure in {GetType().Name} at {GetInvokerAsyncMethodName()}");
return StatusCode(StatusCodes.Status500InternalServerError);
}
}
private async Task<VehicleFloorPricingParameters[]> UpdateVehiceFloorPricingParameters(VehicleFloorPricingParameters[] vehicleFloorPricingParameters)
{
var result = new List<VehicleFloorPricingParameters>();
foreach (var param in vehicleFloorPricingParameters)
{
result.Add(await _parametersService.UpdateAsync(param));
}
return result.ToArray();
}
}
}
@@ -0,0 +1,61 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;
using LCU.API.Collections.CollectionsApiLib.Models;
using LCU.API.Collections.RepoApi.Configuration;
using LCU.Shared.Collections.RepoApiLib.Models.Settings;
namespace LCU.API.Collections.RepoApi.DataAccess
{
[ExcludeFromCodeCoverage]
public class CollectionsDao : DaoClient, ICollectionsDao
{
private readonly RepoApiConfiguration _configuration;
public CollectionsDao(RepoApiConfiguration configuration,
IHttpClientFactory clientFactory) : base(clientFactory)
{
_configuration = configuration;
}
public async Task<TemenosAccount> RetrieveAccountAsync(string accountNo)
{
var url = BuildTemenosAccountUrl(accountNo);
var accounts = await RetrieveByIdentifierAsync<IReadOnlyCollection<TemenosAccount>>(url, accountNo);
if (accounts != null && accounts.Any())
{
return accounts.Where(x => x.AccountIdentifier == accountNo)
.OrderByDescending(x => x.OriginalLoanDate).First();
}
return null;
}
public async Task<IReadOnlyCollection<TemenosHistory>> RetrieveHistoryAsync(int accountId)
{
var url = _configuration.CollectionsLetterUrl.Replace(":accountId", accountId.ToString());
return await RetrieveByIdentifierAsync<IReadOnlyCollection<TemenosHistory>>(url, accountId.ToString());
}
public async Task<IReadOnlyCollection<BranchEntry>> RetrieveBranchSetAsync()
{
var url = _configuration.CoreBranchSetUrl;
return await RetrieveAllAsync<IReadOnlyCollection<BranchEntry>>(url);
}
public async Task<TemenosConfiguration> RetrieveTemenosConfigurationAsync()
{
var url = _configuration.CollectionsConfigUrl;
return await RetrieveAllAsync<TemenosConfiguration>(url);
}
public string BuildTemenosAccountUrl(string accountNo)
{
return _configuration.CollectionAccountUrl + _configuration.OrganizationCode + accountNo;
}
}
}
@@ -0,0 +1,76 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Net.Http;
using System.Threading.Tasks;
using LCU.API.Collections.RepoApi.Configuration;
using LCU.Shared.Collections.RepoApiLib.Models.Consumer;
namespace LCU.API.Collections.RepoApi.DataAccess
{
[ExcludeFromCodeCoverage]
public class CoreDao : DaoClient, ICoreDao
{
private readonly RepoApiConfiguration _configuration;
public CoreDao(RepoApiConfiguration configuration,
IHttpClientFactory clientFactory) : base(clientFactory)
{
_configuration = configuration;
}
public async Task<MemberDetail> RetrieveMemberAsync(string accountNo)
{
var url = CreateUrlFromAccountNo(_configuration.CoreMemberUrl, accountNo);
return await RetrieveByIdentifierAsync<MemberDetail>(url, accountNo);
}
public async Task<LoanDetail> RetrieveLoanAsync(string accountNo)
{
var url = CreateUrlFromAccountNo(_configuration.CoreLoanUrl, accountNo);
return await RetrieveByIdentifierAsync<LoanDetail>(url, accountNo);
}
public async Task<IReadOnlyCollection<CollateralDetail>> RetrieveCollateralAsync(string accountNo)
{
var url = CreateUrlFromAccountNo(_configuration.CoreCollateralUrl, accountNo);
return await RetrieveByIdentifierAsync<IReadOnlyCollection<CollateralDetail>>(url, accountNo);
}
public async Task<IReadOnlyCollection<CoreTransaction>> RetrieveTransactionsAsync(string accountNo)
{
var url = CreateUrlFromAccountNoAndDateRange(_configuration.CoreLoanTransactions, accountNo);
return await RetrieveByIdentifierAsync<IReadOnlyCollection<CoreTransaction>>(url, accountNo);
}
private string CreateUrlFromAccountNo(string inputUrl, string accountNo)
{
var url = inputUrl.Replace(":loanId", accountNo);
return url;
}
private string CreateUrlFromAccountNoAndDateRange(string inputUrl, string accountNo)
{
var url = inputUrl.Replace(":accountNo", accountNo);
url = url.Replace(":startDate", DateTimeForUri(DateTime.Now.AddDays(_configuration.TransactionHistoryStartDateOffset)));
url = url.Replace(":endDate", DateTimeForUri(DateTime.Now));
url += FormatArrayValuesIntoQuery(_configuration.TransactionTypeCodes, _configuration.TransactionTypeCodeQueryPrefix);
return url;
}
private string DateTimeForUri(DateTime dateTime)
{
return dateTime.ToString(_configuration.TransactionHistoryParameterDateFormat);
}
private string FormatArrayValuesIntoQuery(List<string> arrayValues, string queryPrefix)
{
var returnString = "";
foreach (var arrayValue in arrayValues) {
returnString += (queryPrefix + arrayValue);
}
return returnString;
}
}
}
@@ -0,0 +1,85 @@
using System;
using System.Net;
using System.Net.Http;
using System.Threading.Tasks;
using LCU.API.Collections.RepoApi.Exceptions;
using Newtonsoft.Json;
using Serilog;
namespace LCU.API.Collections.RepoApi.DataAccess
{
public abstract class DaoClient
{
private readonly IHttpClientFactory _clientFactory;
protected DaoClient(IHttpClientFactory clientFactory)
{
_clientFactory = clientFactory;
}
public async Task<T> RetrieveByIdentifierAsync<T>(string url, string identifier)
{
// Temporary log to illustrate this working completely
Log.Information($"Retrieve Async called for {typeof(T).Name} with {url}");
using (var client = _clientFactory.CreateClient())
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
try
{
var retrievedObject = JsonConvert.DeserializeObject<T>(
await response.Content.ReadAsStringAsync());
return retrievedObject;
}
catch (Exception ex)
{
Log.Error(ex,$"Error Deserializing {typeof(T).Name} for {identifier}");
throw new ServerErrorException();
}
}
if (response.StatusCode == HttpStatusCode.NotFound)
{
return default(T);
}
Log.Error($"Retrieve {typeof(T).Name} Bad Status Code {response.StatusCode}, for { url}");
throw new MissingRequirementException(typeof(T).Name, $"Failed to retrieve {typeof(T).Name} for {identifier}");
}
}
public async Task<T> RetrieveAllAsync<T>(string url)
{
// Temporary log
Log.Information($"Retrieve All Async called for {typeof(T).Name} with {url}");
using (var client = _clientFactory.CreateClient())
{
var request = new HttpRequestMessage(HttpMethod.Get, url);
var response = await client.SendAsync(request);
if (response.IsSuccessStatusCode)
{
try
{
var retrievedObject = JsonConvert.DeserializeObject<T>(
await response.Content.ReadAsStringAsync());
return retrievedObject;
}
catch (Exception ex)
{
Log.Error(ex,$"Error Deserializing {typeof(T).Name} for {url}");
throw new ServerErrorException();
}
}
Log.Error($"Retrieve {typeof(T).Name} Bad Status Code {response.StatusCode}, for { url}");
throw new MissingRequirementException(typeof(T).Name, $"Failed to retrieve {typeof(T).Name} for {url}");
}
}
}
}
@@ -0,0 +1,15 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using LCU.API.Collections.CollectionsApiLib.Models;
using LCU.Shared.Collections.RepoApiLib.Models.Settings;
namespace LCU.API.Collections.RepoApi.DataAccess
{
public interface ICollectionsDao
{
Task<TemenosAccount> RetrieveAccountAsync(string accountNo);
Task<IReadOnlyCollection<TemenosHistory>> RetrieveHistoryAsync(int accountId);
Task<IReadOnlyCollection<BranchEntry>> RetrieveBranchSetAsync();
Task<TemenosConfiguration> RetrieveTemenosConfigurationAsync();
}
}
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using LCU.Shared.Collections.RepoApiLib.Models.Consumer;
namespace LCU.API.Collections.RepoApi.DataAccess
{
public interface ICoreDao
{
Task<MemberDetail> RetrieveMemberAsync(string accountNo);
Task<LoanDetail> RetrieveLoanAsync(string accountNo);
Task<IReadOnlyCollection<CollateralDetail>> RetrieveCollateralAsync(string accountNo);
Task<IReadOnlyCollection<CoreTransaction>> RetrieveTransactionsAsync(string accountNo);
}
}
@@ -0,0 +1,5 @@
FROM mcr.microsoft.com/dotnet/core/aspnet:2.2
COPY bin/Debug/netcoreapp2.2/publish App/
WORKDIR /App
ENV ASPNETCORE_ENVIRONMENT=Development
ENTRYPOINT ["dotnet", "LCU.API.Collections.RepoApi.dll"]
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class AuctionOnHoldChecker : IAlertChecker
{
private const string RehoStatus = "REHO";
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
var newEventAlerts = new List<EventAlert>();
foreach (var auctionAssignment in slip.AuctionAssignments)
{
if (
slip.UpdatedRepoAccount.Status == RehoStatus
&& auctionAssignment?.HoldStatus != null
&& auctionAssignment.HoldStatus > 0)
{
newEventAlerts.Add(
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = "Auction On Hold",
CreatedBy = "Batch",
DateAdded = DateTime.Now
}
);
}
}
return newEventAlerts.Count > 0 ? newEventAlerts : null;
}
}
}
@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.API.Collections.RepoApi.Domain.Extensions;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.API.Collections.RepoApi.Extensions;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class AuctionVinDifferentChecker : IAlertChecker
{
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
var newEventAlerts = new List<EventAlert>();
var repoManagerVins = slip.UpdatedRepoAccount.GetCollateralVinList();
foreach (var auctionAssignment in slip.AuctionAssignments)
{
if (auctionAssignment?.AutoIMS?.VIN != null
&& !repoManagerVins.Contains(auctionAssignment.AutoIMS.VIN.Trim().ToUpper()))
{
newEventAlerts.Add(
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = "Auction VIN Different",
SubMessage = FormatSubMessage(auctionAssignment.AutoIMS.VIN.Trim().ToUpper(), repoManagerVins),
CreatedBy = "Batch",
DateAdded = DateTime.Now
}
);
}
}
return newEventAlerts.Count > 0 ? newEventAlerts : null;
}
private static string FormatSubMessage(string auctionAssignmentVin, IReadOnlyCollection<string> vins)
{
if (vins.Count() == 1)
{
return
$"Auction Assignment VIN: {auctionAssignmentVin} differs from RepoManager VIN: {vins.First()}";
}
var repoVins = vins.Aggregate((x, y) => x + ", " + y);
return $"Auction Assignment VIN: {auctionAssignmentVin} is not among RepoManager VINs: {repoVins}";
}
}
}
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class CreditAdjustmentChecker : TransactionHistoryChecker
{
private readonly List<string> _watchedStatuses = new List<string> { "O4R", "REPO", "SOLD" };
private readonly List<string> _watchedTransactionTypes = new List<string> { "PDSB", "RPMT" };
private const string Message = "Credit Adjustment Received";
public override List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
return StandardizedCheckForAlert(new AlertProcessingSlip(slip, Message, _watchedStatuses, _watchedTransactionTypes));
}
}
}
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class DebitAdjustmentChecker : TransactionHistoryChecker
{
private readonly List<string> _watchedStatuses = new List<string> { "O4R", "REPO", "SOLD" };
private readonly List<string> _watchedTransactionTypes = new List<string> { "CHST", "PRCT" };
private const string Message = "Debit Adjustment Received";
public override List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
return StandardizedCheckForAlert(new AlertProcessingSlip(slip, Message, _watchedStatuses, _watchedTransactionTypes));
}
}
}
@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class DocumentNotFoundChecker : IAlertChecker
{
private readonly string _document;
private readonly string _ilActionCode;
private readonly string _status;
private readonly string _wiActionCode;
private const string StateWisconsin = "WI";
private const string StateIllinois = "IL";
public DocumentNotFoundChecker(string document, string wiActionCode, string ilActionCode = null,
string status = null)
{
_document = document;
_wiActionCode = wiActionCode.Trim().ToUpper();
_ilActionCode = ilActionCode?.Trim().ToUpper();
_status = status?.Trim().ToUpper();
}
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
if (slip.Letters == null || slip.Member?.Address == null ||
(_status != null && slip.UpdatedRepoAccount.Status != _status ||
slip.UpdatedRepoAccount.Bankruptcy != null))
{
return null;
}
var isLetterMissing = false;
if (slip.Member.Address.State == StateWisconsin)
{
isLetterMissing = slip.Letters.ToList().Find(x => x.ActionCode == _wiActionCode) == null;
}
else if (_ilActionCode != null && slip.Member.Address.State == StateIllinois)
{
isLetterMissing = slip.Letters.ToList().Find(x => x.ActionCode == _ilActionCode) == null;
}
if (isLetterMissing)
{
return new List<EventAlert>
{
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = _document + " Not Found",
CreatedBy = "Batch",
DateAdded = DateTime.Now
}
};
}
return null;
}
}
}
@@ -0,0 +1,11 @@
using System.Collections.Generic;
using LCU.API.Collections.RepoApi.Domain.Processing;
using EventAlert = LCU.Shared.Collections.RepoApiLib.Models.EventAlert;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public interface IAlertChecker
{
List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip);
}
}
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class NoticeOfSurplusDeficiencyNotSentChecker : IAlertChecker
{
private const int OwnershipTypeImportant = 6;
private const int DaysBeforeConsideredNotSent = 2;
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
var updatedRepoAccount = slip.UpdatedRepoAccount;
if (updatedRepoAccount.RefundStatus?.RefundsReceivedDate == null || updatedRepoAccount.DateClosed != null)
{
return null;
}
var letterSentBy = DateTime.Now.AddDays(-DaysBeforeConsideredNotSent).Date;
if (updatedRepoAccount.RefundStatus.RefundsReceivedDate < letterSentBy && updatedRepoAccount.Bankruptcy == null)
{
return new List<EventAlert>
{
new EventAlert()
{
RepoId = updatedRepoAccount.RepoId,
AccountNo = updatedRepoAccount.AccountNo,
Message = "Notice of Surplus Deficiency Letter Not Sent",
CreatedBy = "Batch",
DateAdded = DateTime.Now,
EmailRecipient = "[email protected]",
EmailBody = CreateEmailBody(updatedRepoAccount)
}
};
}
return null;
}
private string CreateEmailBody(RepoAccount repoAccount)
{
var message = "Notice of Surplus Deficiency Not Sent.<br/><br/>";
var member = repoAccount.Borrowers?.First(b => b.OwnershipType == OwnershipTypeImportant);
var memberName = "Member Name: " + member?.IndividualName + "<br/>";
var accountNo = "Account No: " + repoAccount.AccountNo + "<br/>";
return string.Concat(message, memberName, accountNo);
}
}
}
@@ -0,0 +1,118 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.API.Collections.CollectionsApiLib.Models;
using LCU.API.Collections.RepoApi.Domain.Extensions;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.API.Collections.RepoApi.Extensions;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class O4rNoRepoChecker : IAlertChecker
{
private readonly RepoType _repoType;
private const string O4RStatus = "O4R";
private const string EmailDestination = "[email protected]";
public O4rNoRepoChecker(RepoType repoType)
{
_repoType = repoType;
}
private int GetDayCount()
{
switch (_repoType)
{
case RepoType.Voluntary: return 2;
case RepoType.Involuntary: return 60;
case RepoType.PreSkip: return 31;
default: return -1;
}
}
private int GetRepeatDayCount()
{
switch (_repoType)
{
case RepoType.Voluntary: return 1;
case RepoType.Involuntary: return 30;
default: return 0;
}
}
public bool ShouldSendRepeatedAlert(int daysInStatus, int dayCount, int repeatCount)
{
return (
daysInStatus > dayCount
&& (repeatCount > 0)
&& ((daysInStatus - dayCount) % repeatCount == 0)
);
}
public bool IsRepoAssigned(RepoAccount repoAccount)
{
foreach (var collateral in repoAccount.Collaterals)
{
if (collateral.RepoCompanySelections?.FirstOrDefault(x => x.RepoCompanyAssignmentDate != null && x.RepoType == _repoType) != null)
{
return true;
}
}
return false;
}
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
var daysInStatus = slip.UpdatedRepoAccount.GetDaysInStatus(O4RStatus);
if (daysInStatus > 0 && slip.UpdatedRepoAccount.Status == O4RStatus)
{
// In order to reduce duplicate notifications, do not create a notification if no repo is assigned.
// An 'action needed' item will be created for O4R No Repo if no repo is assigned. A notification is redundant.
if (daysInStatus < GetDayCount() || !IsRepoAssigned(slip.UpdatedRepoAccount))
{
return null;
}
if (daysInStatus == GetDayCount() || ShouldSendRepeatedAlert(daysInStatus, GetDayCount(), GetRepeatDayCount()))
{
return new List<EventAlert>
{
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = "O4R No Repo",
CreatedBy = "Batch",
DateAdded = DateTime.Now,
EmailRecipient = _repoType == RepoType.PreSkip
? EmailDestination
: null,
EmailBody = _repoType == RepoType.PreSkip
? CreateEmailBody(slip.UpdatedRepoAccount)
: null
}
};
}
}
return null;
}
private string CreateEmailBody(RepoAccount repoAccount)
{
var message = "PreSkip has ended, please file a Skip Claim.<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/>";
var collateral = repoAccount.Collaterals.FirstOrDefault(c => c.RepoCompanySelections?.FirstOrDefault(x => x.RepoType == _repoType) != null);
var year = "Year: " + collateral.Year + "<br/>";
var make = "Make: " + collateral.Make + "<br/>";
var model = "Model: " + collateral.Model + "<br/>";
var vin = "VIN: " + collateral.Vin + "<br/>";
return string.Concat(message, memberName, accountNo, year, make, model, vin);
}
}
}
@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
using LCU.Shared.Collections.RepoApiLib.Models.Consumer;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class PaymentReceivedChecker : TransactionHistoryChecker
{
private readonly List<string> _watchedStatuses = new List<string> { "O4R", "REPO", "SOLD" };
private readonly List<string> _watchedTransactionTypes = new List<string> { "COFR", "POFF", "EXT", "SPMT", "XPMT" };
private const string EmailDestination = "[email protected]";
public override List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
var newEventAlerts = new List<EventAlert>();
var results = GetRecentTransactionsFromType(_watchedTransactionTypes, slip.TransactionHistory);
if (results != null && _watchedStatuses.Contains(slip.UpdatedRepoAccount.Status))
{
var dueDate = slip.Loan?.NextDueDate.ToString(CultureInfo.CurrentCulture) ?? "Unknown";
foreach (var result in results)
{
newEventAlerts.Add(new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = "Payment Received",
SubMessage = FormatTransactionMessage(result),
CreatedBy = "Batch",
DateAdded = DateTime.Now,
EmailRecipient = EmailDestination,
EmailBody = CreateEmailBody(slip.UpdatedRepoAccount, result, dueDate)
});
}
}
return newEventAlerts.Count > 0 ? newEventAlerts : null;
}
private string CreateEmailBody(RepoAccount repoAccount, CoreTransaction coreTransaction, string nextDueDate)
{
var message = "Loan Payment<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/>";
var paymentAmount = "Payment Amount: " + coreTransaction.amount + "<br/>";
var nextDueDateString = "Next Due Date: " + nextDueDate + "<br/>";
var loanBalance = "Loan Balance: " + coreTransaction.runningBalance + "<br/>";
return string.Concat(message, memberName, accountNo, paymentAmount, nextDueDateString, loanBalance);
}
}
}
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class RedemptionEndedNoTitleOrderedChecker : IAlertChecker
{
private const string RepoStatus = "REPO";
private const int RedemptionExpiredDaysToWait = 7;
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
if (slip.UpdatedRepoAccount.Status.ToUpper() == RepoStatus &&
DateTime.Now.Date > slip.UpdatedRepoAccount.RedemptionExpiredDate?.AddDays(RedemptionExpiredDaysToWait) &&
slip.UpdatedRepoAccount.Collaterals.FirstOrDefault(x => x.RetitledInLandmarkName == null) != null)
{
return new List<EventAlert>
{
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = "Redemption Ended No Title Ordered",
CreatedBy = "Batch",
DateAdded = DateTime.Now
}
};
}
return null;
}
}
}
@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.API.Collections.RepoApi.Domain.Extensions;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.API.Collections.RepoApi.Extensions;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class RepoNotMovedChecker : IAlertChecker
{
private readonly int _dayCount;
private const string RepoStatus = "REPO";
private const int DaysBeforeSendEmail = 14;
private const string EmailDestination = "[email protected]";
public RepoNotMovedChecker(int dayCount = 30)
{
_dayCount = dayCount;
}
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
var daysInStatus = slip.UpdatedRepoAccount.GetDaysInStatus(RepoStatus);
if (slip.UpdatedRepoAccount.Status.ToUpper() != RepoStatus ||
slip.UpdatedRepoAccount.Collaterals == null || !slip.UpdatedRepoAccount.Collaterals.Any()
|| daysInStatus < _dayCount)
{
return null;
}
foreach (var collateral in slip.UpdatedRepoAccount.Collaterals)
{
if (collateral.AuctionAssignmentToCollaterals != null &&
collateral.AuctionAssignmentToCollaterals.Any())
{
var assignment = collateral.AuctionAssignmentToCollaterals
.Where(x => x.AuctionAssignment != null && x.AuctionAssignment.DateSubmitted == null)
.ToList();
if (assignment.Any() && daysInStatus >= _dayCount)
{
return new List<EventAlert>
{
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = "Repo Not Moved",
CreatedBy = "Batch",
DateAdded = DateTime.Now,
EmailRecipient = daysInStatus == DaysBeforeSendEmail
? EmailDestination
: null,
EmailBody = daysInStatus == DaysBeforeSendEmail
? string.Format("{0} Account status REPO, Not Moved for extended time.", slip.UpdatedRepoAccount.AccountNo)
: null
}
};
}
}
}
return null;
}
}
}
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.API.Collections.RepoApi.Domain.Extensions;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.API.Collections.RepoApi.Extensions;
using EventAlert = LCU.Shared.Collections.RepoApiLib.Models.EventAlert;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class RepoNotSoldChecker : IAlertChecker
{
private readonly int _dayCount;
private readonly int _repeatDayCount;
private const string RepoStatus = "REPO";
private const string DestinationEmail = "[email protected]";
public RepoNotSoldChecker(int dayCount = 30, int repeatDayCount = 0)
{
_dayCount = dayCount;
_repeatDayCount = repeatDayCount;
}
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
var status = slip.UpdatedRepoAccount.Status.ToUpper().Trim();
var days = slip.UpdatedRepoAccount.GetDaysInStatus(status);
if (status == RepoStatus && days > 0)
{
if ((days == _dayCount) || days >= _dayCount && ((days - _dayCount) % _repeatDayCount == 0))
{
var member = slip.UpdatedRepoAccount.Borrowers?.First(b => b.OwnershipType == 6);
return new List<EventAlert>
{
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = "Repo Not Sold",
CreatedBy = "Batch",
DateAdded = DateTime.Now,
EmailRecipient = DestinationEmail,
EmailBody = string.Format("{0} {1} Account status REPO, Not Sold for extended time.",
slip.UpdatedRepoAccount.AccountNo, member?.IndividualName)
}
};
}
}
return null;
}
}
}
@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.API.Collections.CollectionsApiLib.Models;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class RtcLetterNotFoundChecker : IAlertChecker
{
private readonly string _document;
private readonly IEnumerable<string> _actionCodes;
private const string O4RStatus = "O4R";
private const string StateWisconsin = "WI";
public RtcLetterNotFoundChecker(string document, IEnumerable<string> actionCodes)
{
_document = document;
_actionCodes = actionCodes.Select(code => code.Trim().ToUpper());
}
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
if (slip.Member?.Address == null || slip.Member.Address.State != StateWisconsin || slip.Letters == null)
{
return null;
}
foreach (var collateral in slip.UpdatedRepoAccount.Collaterals)
{
if (collateral.RepoCompanySelections?.FirstOrDefault(x => x.RepoType == RepoType.Voluntary) != null)
{
return null;
}
}
var letters = slip.Letters;
if (IsLetterMissing(letters) && (slip.UpdatedRepoAccount.Status.ToUpper().Trim() == O4RStatus))
{
return new List<EventAlert>
{
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = _document + " Not Found",
CreatedBy = "Batch",
DateAdded = DateTime.Now
}
};
}
return null;
}
private bool IsLetterMissing(IEnumerable<RepoLetter> letters)
{
var sentLetterCodes = letters.Select(x => x.ActionCode).ToList();
foreach (var actionCode in _actionCodes)
{
if (sentLetterCodes.Contains(actionCode))
{
return false;
}
}
return true;
}
}
}
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class SaleFundsReceivedChecker : TransactionHistoryChecker
{
private readonly List<string> _watchedStatuses = null;
private readonly List<string> _watchedTransactionTypes = new List<string> { "PRCT" };
private const string Message = "Sale Funds Received";
public override List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
return StandardizedCheckForAlert(new AlertProcessingSlip(slip, Message, _watchedStatuses, _watchedTransactionTypes));
}
}
}
@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using LCU.API.Collections.RepoApi.Domain.Extensions;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.API.Collections.RepoApi.Extensions;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class SkipEndChecker : IAlertChecker
{
private readonly int _dayCount;
private const string O4RStatus = "O4R";
public SkipEndChecker(int dayCount = 30)
{
_dayCount = dayCount;
}
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
if (slip.UpdatedRepoAccount.Status == O4RStatus
&& slip.UpdatedRepoAccount.GetDaysInStatus(O4RStatus) >= _dayCount
&& slip.UpdatedRepoAccount.IsCurrentlySkipClaim())
{
return new List<EventAlert>
{
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = "Skip Claim Ended",
CreatedBy = "Batch",
DateAdded = DateTime.Now
}
};
}
return null;
}
}
}
@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using LCU.API.Collections.RepoApi.Domain.Extensions;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.API.Collections.RepoApi.Extensions;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class SkipNotPaidChecker : IAlertChecker
{
private readonly int _dayCount;
private readonly int _repeatDayCount;
private const string O4RStatus = "O4R";
private const string RepoSubStatusPaidSkip = "PSKP";
public SkipNotPaidChecker(int dayCount = 30, int repeatDayCount = 0)
{
_dayCount = dayCount;
_repeatDayCount = repeatDayCount;
}
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
var days = slip.UpdatedRepoAccount.GetDaysInStatus(O4RStatus);
if (slip.UpdatedRepoAccount.Status == O4RStatus
&& days >= _dayCount && slip.UpdatedRepoAccount.IsCurrentlySkipClaim())
{
if (!string.IsNullOrWhiteSpace(slip.UpdatedRepoAccount.SubStatus) &&
slip.UpdatedRepoAccount.SubStatus.Trim().ToUpper() == RepoSubStatusPaidSkip)
{
return null;
}
if ((days == _dayCount) || days >= _dayCount && ((days - _dayCount) % _repeatDayCount == 0))
{
return new List<EventAlert>
{
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = "Skip Not Paid",
CreatedBy = "Batch",
DateAdded = DateTime.Now
}
};
}
}
return null;
}
}
}
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class SkipPaidChecker : IAlertChecker
{
public const string RepoSubStatusPaidSkip = "PSKP";
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
if (slip.UpdatedRepoAccount != null && !string.IsNullOrWhiteSpace(slip.UpdatedRepoAccount.SubStatus) &&
slip.UpdatedRepoAccount.SubStatus.Trim().ToUpper() == RepoSubStatusPaidSkip &&
slip.UpdatedRepoAccount.Status.ToUpper().Trim() != RepoSubStatusPaidSkip)
{
slip.UpdatedRepoAccount.Status = RepoSubStatusPaidSkip; // TODO Does this get saved? Is it ok to do? (NO)
return new List<EventAlert>
{
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = "Skip Claim Paid",
CreatedBy = "Batch",
DateAdded = DateTime.Now
}
};
}
return null;
}
}
}
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class SoldNoProceedsChecker : IAlertChecker
{
private const int DaysToWaitForSold = 5;
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
foreach (var auctionAssignment in slip.AuctionAssignments)
{
if (
auctionAssignment?.AutoIMS?.SoldDate != null
&& auctionAssignment.AutoIMS.SoldDate < DateTime.Now.AddDays(-DaysToWaitForSold)
&& auctionAssignment.AutoIMS.FundsSent == null)
{
return new List<EventAlert>
{
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = "Sold No Proceeds",
CreatedBy = "Batch",
DateAdded = DateTime.Now
}
};
}
}
return null;
}
}
}
@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class StatusChangeChecker : IAlertChecker
{
private readonly string _watchStatus;
private readonly IEnumerable<string> _fromStatusList;
private const string NoStatus = "";
public StatusChangeChecker(string watchStatus, IEnumerable<string> fromStatusList = null)
{
_watchStatus = watchStatus.Trim().ToUpper();
_fromStatusList = fromStatusList;
}
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
var originalStatus = slip.ExistingRepoAccount?.Status.ToUpper().Trim();
var updatedStatus = slip.UpdatedRepoAccount.Status.ToUpper().Trim();
// new account.
if (originalStatus == null)
{
originalStatus = NoStatus;
}
if (updatedStatus == _watchStatus
&& originalStatus != _watchStatus
&& (_fromStatusList == null || _fromStatusList.Contains(originalStatus)))
{
return new List<EventAlert>
{
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = "Status Changed to " + _watchStatus,
SubMessage =
$"Previously {originalStatus}. I confirm that I have updated the Status to {updatedStatus} in RDN.",
CreatedBy = "Batch",
DateAdded = DateTime.Now
}
};
}
return null;
}
}
}
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using LCU.API.Collections.RepoApi.Domain.Extensions;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.API.Collections.RepoApi.Extensions;
using EventAlert = LCU.Shared.Collections.RepoApiLib.Models.EventAlert;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class TimeInStatusChecker : IAlertChecker
{
private readonly string _checkStatus;
private readonly int _dayCount;
private readonly int _repeatDayCount;
public TimeInStatusChecker(string checkStatus, int dayCount = 30, int repeatDayCount = 0)
{
_checkStatus = checkStatus;
_dayCount = dayCount;
_repeatDayCount = repeatDayCount;
}
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
var status = slip.UpdatedRepoAccount.Status.ToUpper().Trim();
var days = slip.UpdatedRepoAccount.GetDaysInStatus(status);
if (days <= 0 || status != _checkStatus)
{
return null;
}
if ((days == _dayCount) || days >= _dayCount && ((days - _dayCount) % _repeatDayCount == 0))
{
return new List<EventAlert>
{
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = days + " days in " + _checkStatus,
CreatedBy = "Batch",
DateAdded = DateTime.Now
}
};
}
return null;
}
}
}
@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
using LCU.Shared.Collections.RepoApiLib.Models.Consumer;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public abstract class TransactionHistoryChecker : IAlertChecker
{
private readonly int _daysInPastToSearch;
protected TransactionHistoryChecker(int daysInPastToSearch = 3)
{
_daysInPastToSearch = daysInPastToSearch;
}
public abstract List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip);
protected List<EventAlert> StandardizedCheckForAlert(AlertProcessingSlip alertProcessingSlip)
{
var newEventAlerts = new List<EventAlert>();
var results = GetRecentTransactionsFromType
(
alertProcessingSlip.WatchedTransactionTypes,
alertProcessingSlip.AccountProcessingSlip.TransactionHistory
);
if (results != null && AccountStatusIsQualified(alertProcessingSlip.WatchedStatusTypes,
alertProcessingSlip.AccountProcessingSlip.UpdatedRepoAccount.Status))
{
foreach (var result in results)
{
newEventAlerts.Add(new EventAlert()
{
RepoId = alertProcessingSlip.AccountProcessingSlip.UpdatedRepoAccount.RepoId,
AccountNo = alertProcessingSlip.AccountProcessingSlip.UpdatedRepoAccount.AccountNo,
Message = alertProcessingSlip.Message,
SubMessage = FormatTransactionMessage(result),
CreatedBy = "Batch",
DateAdded = DateTime.Now
}
);
}
}
return newEventAlerts.Count > 0 ? newEventAlerts : null;
}
private static bool AccountStatusIsQualified(List<string> watchedStatusTypes, string status)
{
return watchedStatusTypes?.Contains(status.ToUpper()) ?? true;
}
public static string FormatTransactionMessage(CoreTransaction transaction)
{
var message = "";
if (transaction.transactionTypeCode.ToLower().Contains("undo"))
{
message += "***TRANSACTION UNDONE*** - ";
}
return message +
$"Transaction Type: \"{transaction.transactionTypeCode}\" | Posting: {transaction.postingDate} | Transaction: {transaction.amount?.ToString("C2")} | Balance: {transaction.runningBalance?.ToString("C2")} | Description: \"{transaction.transactionTypeDescription}\"";
}
protected List<CoreTransaction> GetRecentTransactionsFromType(List<string> transactionTypes, IReadOnlyCollection<CoreTransaction> transactions)
{
var returnItems = new List<CoreTransaction>();
if (transactions != null && transactions.Any())
{
foreach (var transactionType in transactionTypes)
{
returnItems.AddRange(GetRecentTransactionsFromType(transactionType, _daysInPastToSearch, transactions));
}
}
return returnItems.Count > 0 ? returnItems : null;
}
private static IEnumerable<CoreTransaction> GetRecentTransactionsFromType(string transactionType, int daysInPast, IReadOnlyCollection<CoreTransaction> transactions)
{
return (
from transaction in transactions
where (
transaction.postingDate > DateTime.Now.AddDays(-daysInPast)
&& transaction.transactionTypeCode.ToLower().Contains(transactionType.ToLower()
)
)
select transaction
).ToList();
}
}
}
@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.API.Collections.CollectionsApiLib.Models;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.Extensions
{
public static class RepoAccountExtensions
{
public static bool HasAnyCollateral(this RepoAccount repoAccount)
{
return repoAccount?.Collaterals != null && repoAccount.Collaterals.Any();
}
public static IReadOnlyCollection<string> GetCollateralVinList(this RepoAccount repoAccount)
{
if (repoAccount?.Collaterals != null && repoAccount.Collaterals.Any())
{
return repoAccount.Collaterals.Select(x => x.Vin?.Trim().ToUpper()).ToList();
}
return new List<string>();
}
public static IReadOnlyCollection<int> GetUniqueAuctionAssignmentIds(this RepoAccount repoAccount)
{
var aaList = new List<int>();
if (repoAccount?.Collaterals == null || !repoAccount.Collaterals.Any()) return aaList;
foreach (var collateral in repoAccount.Collaterals)
{
if (collateral.AuctionAssignmentToCollaterals?.Any() != true) continue;
foreach (var toCollateral in collateral.AuctionAssignmentToCollaterals)
{
aaList.Add(toCollateral.AuctionAssignmentId);
}
}
return aaList;
}
public static int GetDaysInStatus(this RepoAccount repoAccount, string status)
{
if (repoAccount?.RepoStatusChanges == null || !repoAccount.RepoStatusChanges.Any())
{
return -1;
}
var lastStatus = repoAccount.RepoStatusChanges.LastOrDefault(x => x.StatusTo == status);
if (lastStatus != null)
{
return (DateTime.Now - lastStatus.ChangedDate).Days;
}
return -1;
}
public static bool IsCurrentlySkipClaim(this RepoAccount updatedRepoAccount)
{
foreach (var collateral in updatedRepoAccount.Collaterals)
{
if (collateral.RepoCompanySelections == null || !collateral.RepoCompanySelections.Any()) continue;
if (collateral.RepoCompanySelections?.OrderByDescending(x => x.DateTimeCreated).First().RepoType ==
RepoType.Skip)
{
return true;
}
}
return false;
}
}
}
@@ -0,0 +1,84 @@
using System.Linq;
using LCU.API.Collections.CollectionsApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.Extensions
{
public static class TemenosAccountExtensions
{
public const string UserStatusFieldOld = "User Status Code 1";
public const int OpenCaseStatus = 33;
public const int CaseTypeRepossession = 7;
public static bool HasOldIntellectStatus(this TemenosAccount recoveryAccount)
{
return recoveryAccount?.CustomFieldMap != null
&& recoveryAccount.CustomFieldMap.ContainsKey(UserStatusFieldOld)
&& !string.IsNullOrWhiteSpace(recoveryAccount.CustomFieldMap[UserStatusFieldOld]);
}
/**
* CaseId of 0 corresponds to "An open case with a valid RepoStatus"
*/
public static bool HasRecoveryCase(this TemenosAccount recoveryAccount, int caseId)
{
if (recoveryAccount?.Cases == null || !recoveryAccount.Cases.Any())
{
return false;
}
return caseId == 0 ? recoveryAccount.HasOpenRecoveryCase() : recoveryAccount.HasSpecificCase(caseId);
}
public static bool HasOpenRecoveryCase(this TemenosAccount recoveryAccount)
{
TemenosCase selectedCase = null;
if (recoveryAccount?.Cases == null || !recoveryAccount.Cases.Any())
{
return false;
}
selectedCase = recoveryAccount.Cases
.FirstOrDefault(x => x.CaseStatusId == OpenCaseStatus && x.RepoStatus != RepoStatus.UNKNOWN);
return (selectedCase != null);
}
public static bool HasSpecificCase(this TemenosAccount recoveryAccount, int caseId)
{
TemenosCase selectedCase = null;
if (recoveryAccount?.Cases == null || !recoveryAccount.Cases.Any())
{
return false;
}
selectedCase = recoveryAccount.Cases.FirstOrDefault(x => x.CaseId == caseId);
return (selectedCase != null);
}
public static TemenosCase FindRecoveryCaseOrDefault(this TemenosAccount recoveryAccount, int caseId)
{
TemenosCase selectedCase = null;
if (recoveryAccount?.Cases == null || !recoveryAccount.Cases.Any())
{
return null;
}
if (caseId > 0)
{
selectedCase = recoveryAccount.Cases.FirstOrDefault(x => x.CaseId == caseId);
}
else
{
selectedCase = recoveryAccount.Cases
.Where(x => x.CaseStatusId == OpenCaseStatus && x.RepoStatus != RepoStatus.UNKNOWN
&& x.CaseTypeId == CaseTypeRepossession && x.RepoStatus != RepoStatus.INELIGIBLE)
.OrderByDescending(x => x.CreatedDate).FirstOrDefault();
}
return selectedCase;
}
}
}
@@ -0,0 +1,21 @@
using System.Collections.Generic;
using LCU.API.Collections.RepoApi.Domain.FloorPricing.Strategies;
namespace LCU.API.Collections.RepoApi.Domain.FloorPricing
{
public class FloorPricingStrategyFactory : IFloorPricingStrategyFactory
{
public FloorPricingStrategyFactory()
{
}
public IReadOnlyCollection<IFloorPricingStrategy> GetFloorPricingStrategies()
{
return new List<IFloorPricingStrategy>()
{
new AutoImsSuggestedValue(),
new NadaMmrConditionValue()
};
}
}
}
@@ -0,0 +1,13 @@
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.FloorPricing
{
public interface IFloorPricingStrategy
{
FloorPriceCalculationResult CalculateFloorPrice(FloorPriceCalculationParams fpcParams);
int GetStrategyWeight();
// Requirements and Qualifications
}
}
@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace LCU.API.Collections.RepoApi.Domain.FloorPricing
{
public interface IFloorPricingStrategyFactory
{
IReadOnlyCollection<IFloorPricingStrategy> GetFloorPricingStrategies();
}
}
@@ -0,0 +1,43 @@
using System;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.FloorPricing.Strategies
{
/**
* This strategy uses the suggested price from the ImsAutoFileData entry for the auction.
*/
public class AutoImsSuggestedValue : IFloorPricingStrategy
{
public AutoImsSuggestedValue() { }
public FloorPriceCalculationResult CalculateFloorPrice(FloorPriceCalculationParams fpcParams)
{
var result = new FloorPriceCalculationResult()
{
StrategyName = this.GetType().FullName,
Success = false
};
if (fpcParams?.ImsAutoFileData?.AuctionGuideBookPrice != null)
{
// Price must be rounded to nearest $5 per business.
var inputPrice = (double)(fpcParams.ImsAutoFileData.AuctionGuideBookPrice ?? 0);
result.FloorPrice = (decimal) (Math.Round( inputPrice / 5.0) * 5);
result.Success = result.FloorPrice > 0;
result.FailureReason = result.Success ? "" : "Failed attempt to use auction guide book price.";
}
else
{
result.Success = false;
result.FailureReason = "Auction Guide Book Price missing.";
}
return result;
}
public int GetStrategyWeight()
{
return 0; // This is NOT the preferred strategy.
}
}
}
@@ -0,0 +1,144 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.Shared.Collections.RepoApiLib.Models;
using LCU.Shared.Collections.RepoApiLib.WebServices.NADA;
using Microsoft.EntityFrameworkCore.Internal;
using Enumerable = System.Linq.Enumerable;
namespace LCU.API.Collections.RepoApi.Domain.FloorPricing.Strategies
{
/**
* This strategy uses the AutoIms MMR values, the values from Nada, and the condition from AutoIms along
* with a specific formula, to calculate a floor.
*/
public class NadaMmrConditionValue : IFloorPricingStrategy
{
public FloorPriceCalculationResult CalculateFloorPrice(FloorPriceCalculationParams fpcParams)
{
var result = new FloorPriceCalculationResult()
{
StrategyName = this.GetType().FullName,
Success = false,
ResultWeight = -1
};
if (fpcParams?.CollateralValuation == null || fpcParams?.ImsAutoFileData == null ||
fpcParams?.VehicleFloorPricingParameters == null
|| fpcParams?.VehicleFloorPricingParameters?.Any() == false)
{
// May want to log here, or set a reason code on the result.
result.FailureReason = "Missing ImsAuto File, Pricing Parameters, or Collateral Valuation";
return result;
}
// 1. use condition to find the correct params.
// + VehicleDemicalGradeCode is ignored, ApprovedVehicleGrade is preferred, and Vehicle Grade is the fallback.
var condition = fpcParams.ImsAutoFileData.ApprovedVehicleGrade ?? fpcParams.ImsAutoFileData.VehicleGrade;
if (condition <= 0 || condition == null)
{
result.FailureReason = "No Condition In AutoIMS File Data";
return result;
}
// 2. Get the matching floor pricing calculation row.
var floorPricing = Enumerable.First(Enumerable.Where(fpcParams.VehicleFloorPricingParameters,
x => condition >= x.LowCondition && condition <= x.HighCondition));
// 3. Get the floor pricing params based on matching condition.
var mmrValue = GetMMRValueByParam(floorPricing.MMRAdjustedValue, fpcParams.ImsAutoFileData,
fpcParams.ImsAutoFileData.AuctionGuideBookPrice ?? 0);
List<Decimal> nadaValues = new List<decimal>();
nadaValues.Add(GetNadaValueByParam(floorPricing.NADAFirstValue, fpcParams.CollateralValuation));
nadaValues.Add(GetNadaValueByParam(floorPricing.NADASecondValue, fpcParams.CollateralValuation));
bool onlyZeroNadaValues = nadaValues.TrueForAll(x => x == 0);
if (fpcParams.CollateralValuation.Year <= (DateTime.Now.AddYears(-7).Year) || onlyZeroNadaValues)
{
nadaValues.Add(fpcParams.CollateralValuation.TradeIn);
onlyZeroNadaValues = nadaValues.TrueForAll(x => x == 0);
}
// If there are no NADA or MMR prices, fail the strategy
if (onlyZeroNadaValues && (mmrValue == 0 || mmrValue == fpcParams.ImsAutoFileData.AuctionGuideBookPrice))
{
result.FailureReason = "No NADA or MMR prices returned";
return result;
}
// 4. Run the formula
var nadaValue = GetNadaAvgValue(nadaValues);
var divisor = mmrValue == 0 || nadaValue == 0
? 1
: 2;
var baseResult = (mmrValue + nadaValue) / divisor;
baseResult = (baseResult * floorPricing.PercentageOf) / 100;
baseResult = Math.Round(baseResult / 5.0m) * 5;
result.FloorPrice = baseResult;
result.Success = true;
result.ResultWeight = GetStrategyWeight();
return result;
}
public decimal GetNadaAvgValue(List<Decimal> nadaValues)
{
Decimal aggregateValue = 0;
int valueCount = 0;
Decimal aggregateAvgValue = 0;
if (nadaValues.Count() > 0)
{
foreach (var nadaVal in nadaValues)
{
if (nadaVal > 0)
{
aggregateValue += nadaVal;
valueCount++;
}
}
aggregateAvgValue = aggregateValue / valueCount;
}
return aggregateAvgValue;
}
private decimal GetNadaValueByParam(string floorPricingNadaFirstValue,
CollateralValuation fpcParamsCollateralValuation)
{
if (floorPricingNadaFirstValue.ToLower().StartsWith("low"))
{
return fpcParamsCollateralValuation.LowAdjustedAuctionValue;
}
if (floorPricingNadaFirstValue.ToLower().StartsWith("avg"))
{
return fpcParamsCollateralValuation.AverageAdjustedAuctionValue;
}
return fpcParamsCollateralValuation.HighAdjustedAuctionValue;
}
private decimal GetMMRValueByParam(string floorPricingMmrValue, ImsAutoFileData fpcParamsImsAutoFileData,
decimal defaultValue)
{
if (floorPricingMmrValue.ToLower().StartsWith("regional"))
{
return fpcParamsImsAutoFileData.MMRRegionalAdjustedPrice ?? defaultValue;
}
return fpcParamsImsAutoFileData.MMRNationalAdjustedPrice ?? defaultValue;
}
public int GetStrategyWeight()
{
return 99; // This is the preferred strategy.
}
}
}
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using LCU.API.Collections.CollectionsApiLib.Models;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Services
{
public interface IRedemptionExpiredDateCalculator
{
DateTime? CalculateAsync(TemenosCase temenosCase,
IReadOnlyCollection<RepoLetter> readOnlyCollection);
}
}
@@ -0,0 +1,26 @@
using System.Collections.Generic;
namespace LCU.API.Collections.RepoApi.Domain.Processing
{
public class AlertProcessingSlip
{
public RepoAccountProcessingSlip AccountProcessingSlip { get; set; }
public string Message { get; set; }
public List<string> WatchedStatusTypes { get; set; }
public List<string> WatchedTransactionTypes { get; set; }
public AlertProcessingSlip
(
RepoAccountProcessingSlip accountProcessingSlip,
string message,
List<string> watchedStatusTypes,
List<string> watchedTransactionTypes
)
{
AccountProcessingSlip = accountProcessingSlip;
Message = message;
WatchedStatusTypes = watchedStatusTypes;
WatchedTransactionTypes = watchedTransactionTypes;
}
}
}
@@ -0,0 +1,96 @@
using System.Collections.Generic;
using LCU.API.Collections.CollectionsApiLib.Models;
using LCU.API.Collections.RepoApi.Exceptions;
using LCU.Shared.Collections.RepoApiLib.Models;
using LCU.Shared.Collections.RepoApiLib.Models.Consumer;
using LCU.Shared.Collections.RepoApiLib.Models.Producer;
using LCU.Shared.Collections.RepoApiLib.Models.Settings;
namespace LCU.API.Collections.RepoApi.Domain.Processing
{
/**
* This class represents everything you need to do a logical update or creation of a RepoAccount
*/
public class RepoAccountProcessingSlip
{
public ProcessingItem RequestedBy { get; set; }
public string AccountNo { get; set; }
public int CaseId { get; set; }
public TemenosAccount RecoveryAccount { get; set; }
public TemenosCase RecoveryCase { get; set; }
public RepoAccount ExistingRepoAccount { get; set; }
public RepoAccount UpdatedRepoAccount { get; set; }
public Settings CurrentSettings { get; set; }
public IReadOnlyCollection<CoreTransaction> TransactionHistory { get; set; }
public MemberDetail Member { get; set; }
public LoanDetail Loan { get; set; }
public IReadOnlyCollection<AuctionAssignment> AuctionAssignments { get; set; }
public IReadOnlyCollection<RepoLetter> Letters { get; set; }
public void AssertReadyForPreChecks()
{
if (RequestedBy == null)
{
throw new MissingRequirementException("Processing Item",
$"RepoAccount Processing Slip requires a processing item.");
}
if (RecoveryAccount == null)
{
throw new MissingRequirementException("Recovery Account",
$"{RequestedBy.AccountNo} did not have account.");
}
if (CurrentSettings == null)
{
throw new MissingRequirementException("Settings",
$"RepoAccount Processing Slip requires settings.");
}
}
public void AssertReadyForFullProcessing()
{
AssertReadyForPreChecks();
if (RecoveryCase == null)
{
throw new MissingRequirementException("Recovery Case",
$"{RecoveryAccount.AccountId} did not have case {CaseId}.");
}
if (Loan == null)
{
throw new MissingRequirementException("Loan", "Loan required to process.");
}
if (Letters == null)
{
throw new MissingRequirementException("Letters", "Letters required to process.");
}
}
public void AssertReadyForAlertChecks()
{
AssertReadyForFullProcessing();
if (TransactionHistory == null && ExistingRepoAccount.ReferenceStatus != "Chargeoff")
{
throw new MissingRequirementException("Transactions", "Transactions required to process alerts.");
}
if (Member == null)
{
throw new MissingRequirementException("Member", "Member required to process alerts.");
}
if (AuctionAssignments == null)
{
throw new MissingRequirementException("Auction Assignments", "Auction Assignments required to process alerts.");
}
}
}
}
@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.API.Collections.CollectionsApiLib.Models;
using LCU.API.Collections.RepoApi.Services;
using LCU.Shared.Collections.RepoApiLib.Models;
using Serilog;
namespace LCU.API.Collections.RepoApi.Domain
{
public class RedemptionExpiredDateCalculator : IRedemptionExpiredDateCalculator
{
public DateTime? CalculateAsync(TemenosCase temenosCase, IReadOnlyCollection<RepoLetter> letters)
{
try
{
// !!!!! 04/15/2021, Bill and Kyle don't want to check for letters, they just want to use the date.
// var redemptionLettersWi = letters.Where(x => x.ActionCode == "LTR35" || x.ActionCode == "LTR34" ).ToList();
// var redemptionLettersIl = letters.Where(x => x.ActionCode == "703" || x.ActionCode == "701").ToList();
// if (redemptionLettersIl.Any() && redemptionLettersWi.Any())
// {
// return null;
// }
if (temenosCase.RedemptionExpiredDateWi != null) { // redemptionLettersWi.Any())}
return temenosCase.RedemptionExpiredDateWi;
}
if( temenosCase.RedemptionExpiredDateIl != null) { // redemptionLettersIl.Any())}
return temenosCase.RedemptionExpiredDateIl;
}
}
catch (Exception ex)
{
Log.Error(ex, $"Failed To Get Redemption Exp. Date. TemenosCaseID: {temenosCase.CaseId}", temenosCase);
}
return null;
}
}
}
@@ -0,0 +1,11 @@
using System;
namespace LCU.API.Collections.RepoApi.Exceptions
{
public class AccountCreationFailed : ApplicationException
{
public AccountCreationFailed(string message) : base(message)
{
}
}
}
@@ -0,0 +1,19 @@
using System;
namespace LCU.API.Collections.RepoApi.Exceptions
{
/// <summary>
/// Describes a missing requirement for processing an account; this account CANNOT be tried again later.
/// </summary>
public class IneligibleAccountException : Exception
{
public string Field { get; set; }
public string Reason { get; set; }
public IneligibleAccountException(string field, string reason) : base(field)
{
Field = field;
Reason = reason;
}
}
}
@@ -0,0 +1,19 @@
using System;
namespace LCU.API.Collections.RepoApi.Exceptions
{
/// <summary>
/// Describes a missing requirement for processing an account; which will mean the account can be tried again later.
/// </summary>
public class MissingRequirementException : Exception
{
public string Field { get; set; }
public string Advice { get; set; }
public MissingRequirementException(string field, string advice) : base(field)
{
Field = field;
Advice = advice;
}
}
}
@@ -0,0 +1,15 @@
using System;
namespace LCU.API.Collections.RepoApi.Exceptions
{
/**
* If an entity cannot be found, that specific error can allow for a 404 or 400 with more detail to be
* returned. So specify that to the API layer.
*/
public class NotFoundException : ApplicationException
{
public NotFoundException(string message) : base(message)
{
}
}
}
@@ -0,0 +1,15 @@
using System;
namespace LCU.API.Collections.RepoApi.Exceptions
{
/**
* Do not want to expose database and related exceptions to the controllers / web layer.
* The lower layers log what happened for developers, then return a general error; unless
* a more specific error can be returned in a way to allow the client to act (such as NotFound).
*/
// IF YOU SEE THIS EXCEPTION IN SEQ, IT MEANS YES, YOU HAVE TO INVESTIGATE.
public class ServerErrorException : Exception
{
public ServerErrorException(): base("[INVESTIGATE] A server error occured. See associated logs.") { }
}
}
@@ -0,0 +1,21 @@
using System.Collections.Generic;
using System.Linq;
using Microsoft.AspNetCore.Mvc.ModelBinding;
namespace LCU.API.Collections.RepoApi.Extensions
{
public static class ModelStateMessages
{
public static List<string> GetErrorMessages(this ModelStateDictionary dictionary)
{
if (dictionary == null)
{
return new List<string>();
}
return dictionary.SelectMany(m => m.Value.Errors)
.Select(m => m.ErrorMessage)
.ToList();
}
}
}
@@ -0,0 +1,85 @@
using LCU.API.Collections.RepoApi.HostedServices;
using LCU.API.Collections.RepoApi.Services;
using LCU.API.Collections.RepoApi.Services.AutoIms;
using LCU.API.Collections.RepoApi.Services.Producer;
using LCU.API.Collections.RepoApi.Services.Reporting;
using LCU.Shared.Collections.RepoApiLib.Repositories;
using LCU.Shared.Collections.RepoApiLib.Repositories.Settings;
using LCU.Shared.Collections.RepoApiLib.WebServices.NADA;
using Microsoft.Extensions.DependencyInjection;
namespace LCU.API.Collections.RepoApi.Extensions
{
public static class RepoApiStartup
{
public static void UseRepoServices(this IServiceCollection services)
{
services.AddScoped<IAccountAlertCheckService, AccountAlertCheckService>();
services.AddScoped<IAuctionAssignmentService, AuctionAssignmentService>();
services.AddScoped<IAuctionFtpClientService, AuctionFtpClientService>();
services.AddScoped<IAuctionHouseService, AuctionHouseService>();
services.AddScoped<IAuctionReportingService, AuctionReportingService>();
services.AddScoped<IAuditRecordService, AuditRecordService>();
services.AddScoped<IAutoImsAuctionProcessorService, AutoImsAuctionProcessorService>();
services.AddScoped<IAutoImsDownloaderService, AutoImsDownloaderService>();
services.AddScoped<IAutoImsImporterService, AutoImsImporterService>();
services.AddScoped<IAutoImsNoteProcessorService, AutoImsNoteProcessorService>();
services.AddScoped<ICollateralService, CollateralService>();
services.AddScoped<ICollateralValueService, CollateralValueService>();
services.AddScoped<IRepoAccountProcessorService, RepoAccountProcessorService>();
services.AddScoped<IEventAlertService, EventAlertService>();
services.AddScoped<IFactoidService, FactoidService>();
services.AddScoped<IFloorPricingService, FloorPricingService>();
services.AddScoped<IImsAutoFileDataConverter, ImsAutoFileDataConverter>();
services.AddScoped<IImsAutoFileDataService, ImsAutoFileDataService>();
services.AddScoped<IImsAutoFileNoteConverter, ImsAutoFileNoteConverter>();
services.AddScoped<ILandmarkReportingService, LandmarkReportingService>();
services.AddScoped<IRefundStatusService, RefundStatusService>();
services.AddScoped<IRepoAccountService, RepoAccountService>();
services.AddScoped<IRepoCompanyService, RepoCompanyService>();
services.AddScoped<IRepoCompanySelectionService, RepoCompanySelectionService>();
services.AddScoped<IRepoCompanyReportingService, RepoCompanyReportingService>();
services.AddScoped<IRepoLetterService, RepoLetterService>();
services.AddScoped<IRepoQueueProducerService, RepoQueueProducerService>();
services.AddScoped<IRepoStatusChangeService, RepoStatusChangeService>();
services.AddScoped<ISavedLocationService, SavedLocationService>();
services.AddScoped<ISettingsService, SettingsService>();
services.AddScoped<IVehicleFloorPricingParametersService, VehicleFloorPricingParametersService>();
services.AddScoped<IProcessingReceiptService, ProcessingReceiptService>();
}
public static void UseRepoRepositories(this IServiceCollection services)
{
services.AddScoped<IAuditRecordRepository, AuditRecordRepository>();
services.AddScoped<IAccountTypeRepository, AccountTypeRepository>();
services.AddScoped<IAllowedStatusChangeRepository, AllowedStatusChangeRepository>();
services.AddScoped<IAuctionAssignmentRepository, AuctionAssignmentRepository>();
services.AddScoped<IAuctionAssignmentNoteRepository, AuctionAssignmentNoteRepository>();
services.AddScoped<IBodyStyleRepository, BodyStyleRepository>();
services.AddScoped<ICleaningLevelRepository, CleaningLevelRepository>();
services.AddScoped<ICollateralRepository, CollateralRepository>();
services.AddScoped<ICollateralTypeRepository, CollateralTypeRepository>();
services.AddScoped<ICollateralTypeToVehicleTypeRepository, CollateralTypeToVehicleTypeRepository>();
services.AddScoped<IEventAlertRepository, EventAlertRepository>();
services.AddScoped<IHoldStatusRepository, HoldStatusRepository>();
services.AddScoped<IImsAutoFileDataRepository, ImsAutoFileDataRepository>();
services.AddScoped<ILetterRepository, LetterRepository>();
services.AddScoped<ILoanCategoryRepository, LoanCategoryRepository>();
services.AddScoped<ILocationTypeRepository, LocationTypeRepository>();
services.AddScoped<IRepoStatusChangeRepository, RepoStatusChangeRepository>();
services.AddScoped<IRepoAccountRepository, RepoAccountRepository>();
services.AddScoped<IRepoCompanyRepository, RepoCompanyRepository>();
services.AddScoped<IVehicleTypeRepository, VehicleTypeRepository>();
services.AddScoped<IVehicleFloorPricingParametersRepository, VehicleFloorPricingParametersRepository>();
services.AddScoped<IAuctionHouseRepository, AuctionHouseRepository>();
services.AddScoped<IReportingRepository, ReportingRepository>();
services.AddScoped<IRefundStatusRepository, RefundStatusRepository>();
services.AddScoped<IRepoCompanySelectionRepository, RepoCompanySelectionRepository>();
services.AddScoped<IRepoManualStatusRepository, RepoManualStatusRepository>();
services.AddScoped<ISavedLocationRepository, SavedLocationRepository>();
services.AddScoped<IProcessingReceiptRepository, ProcessingReceiptRepository>();
services.AddScoped<IExternalUserRepository, ExternalUserRepository>();
}
}
}
@@ -0,0 +1,202 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using LCU.Shared.Collections.RepoApiLib.Formatting;
using LCU.Shared.Collections.RepoApiLib.Models.Reporting.Responses;
namespace LCU.API.Collections.RepoApi.Factories
{
public enum ReportSumType { WholeNumber, Currency, WholeNumberAvg, CurrencyAvg, PercentageAvg }
public static class ReportTableFactory
{
public static ReportTable CreateHistoricalSummationTable<T>(
ReportSumType reportType,
IEnumerable<T> dataRows,
string pluralEntityName,
DateTime lowDateTime,
DateTime highDateTime,
Func<T, decimal?> cellValue,
Func<T, string> uniqueRowDisplay,
Func<T, DateTime?> dateTimeField,
Func<T, object> rowKey,
bool showAbsenceRow = true
)
{
var totalMonths = CalculateTotalMonths(lowDateTime, highDateTime);
var rows = CreateUniqueRowNames(dataRows, uniqueRowDisplay, pluralEntityName, showAbsenceRow);
var reportTable = new ReportTable()
{
Headers = CreateMonthHeaders(lowDateTime, highDateTime, new string[] { "" }, new string[] { "Total" }),
Cells = new ReportCell[rows.Count(), totalMonths + 2]
};
// Build the Report Table Values
var uniqueRowIdentifiers = new List<object>();
var rawValueTableLengths = new int[rows.Count(), totalMonths + 1];
var rawValueTable = new decimal[rows.Count(), totalMonths + 1];
foreach (var currentDataRow in dataRows)
{
var rowDisplay = uniqueRowDisplay(currentDataRow);
var currentKey = rowKey(currentDataRow);
var currentValue = cellValue(currentDataRow);
var currentDateOfRow = dateTimeField(currentDataRow);
if (currentDateOfRow != null &&
currentValue != null &&
!uniqueRowIdentifiers.Contains(currentKey))
{
uniqueRowIdentifiers.Add(currentKey);
var monthIndex = CalculateMonthOffset(lowDateTime, currentDateOfRow.Value);
var currentItemIndex = FindUniqueRowIndex(rows, rowDisplay);
// "All Entities" Value
rawValueTable[0, monthIndex] += currentValue.Value; // Totals for Month
rawValueTable[0, totalMonths] += currentValue.Value; // Totals for All
// "All Entities" Length
rawValueTableLengths[0, monthIndex] += 1; // Totals for Month
rawValueTableLengths[0, totalMonths] += 1; // Totals for All
// Current Auction Row
if (currentItemIndex != 0)
{
rawValueTable[currentItemIndex, monthIndex] += currentValue.Value; // Total for Month
rawValueTable[currentItemIndex, totalMonths] += currentValue.Value; // Total for Auction House.
rawValueTableLengths[currentItemIndex, monthIndex] += 1; // Total for Month
rawValueTableLengths[currentItemIndex, totalMonths] += 1; // Total for Auction House.
}
}
}
// Convert Report Table into ReportCells
for (var i = 0; i < rawValueTable.GetLength(0); i++)
{
for (var j = 0; j < rawValueTable.GetLength(1); j++)
{
var rawValue = rawValueTable[i, j];
var columnIndex = j + 1; // Add 1 to compensate for Row Display Column
if (reportType == ReportSumType.WholeNumberAvg ||
reportType == ReportSumType.PercentageAvg ||
reportType == ReportSumType.CurrencyAvg)
{
var rawValueLength = rawValueTableLengths[i, j];
if (rawValueLength != 0)
{
rawValue = rawValue / rawValueLength;
}
}
switch(reportType)
{
case ReportSumType.WholeNumberAvg:
case ReportSumType.WholeNumber:
reportTable.Cells[i, columnIndex] = ReportCellFormatting.CreateIntCell(Convert.ToInt32(rawValue));
break;
case ReportSumType.Currency:
case ReportSumType.CurrencyAvg:
reportTable.Cells[i, columnIndex] = ReportCellFormatting.CreateCurrencyCell(rawValue);
break;
case ReportSumType.PercentageAvg:
reportTable.Cells[i, columnIndex] = ReportCellFormatting.CreatePercentageCell(rawValue);
break;
}
}
}
// Add Auction Names to Left Column
for (var i = 0; i < rows.Count(); i++)
{
reportTable.Cells[i, 0] = ReportCellFormatting.CreateStringCell(rows.ElementAt(i));
}
return reportTable;
}
public static int FindUniqueRowIndex(IEnumerable<string> uniqueRows, string columnName)
{
if (columnName != null)
{
foreach (var item in uniqueRows.Select((value, index) => new { index, value }))
{
if (item.value == columnName)
{
return item.index;
}
}
}
return uniqueRows.Count() - 1;
}
private static IEnumerable<string> CreateUniqueRowNames<T>(IEnumerable<T> reportData, Func<T, string> uniqueRowDisplay, string pluralEntityName, bool showAbsenceRow = true)
{
var list = new List<string>();
list.Add("All " + pluralEntityName);
if (uniqueRowDisplay != null)
{
foreach (var row in reportData)
{
var rowName = uniqueRowDisplay(row);
if (rowName != null)
{
if (!list.Contains(rowName))
{
list.Add(rowName);
}
}
}
}
if (showAbsenceRow)
{
list.Add("No " + pluralEntityName);
}
return list;
}
private static string[] CreateMonthHeaders(DateTime lowDateTime, DateTime highDateTime, string[] preHeaders = null, string[] postHeaders = null)
{
var totalMonths = CalculateTotalMonths(lowDateTime, highDateTime);
var preHeadersLength = preHeaders?.Length ?? 0;
var postHeadersLength = postHeaders?.Length ?? 0;
string[] monthHeaders = new string[totalMonths + preHeadersLength + postHeadersLength];
for (var i = 0; i < preHeadersLength; i++)
{
monthHeaders[i] = preHeaders[i];
}
for (var i = 0; i < totalMonths; i++)
{
var currentDate = lowDateTime.AddMonths(i);
monthHeaders[preHeadersLength + i] = $"{currentDate.ToString("MMM", CultureInfo.InvariantCulture)} {currentDate.Year}";
}
for (var i = 0; i < postHeadersLength; i++)
{
monthHeaders[preHeadersLength + totalMonths + i] = postHeaders[i];
}
return monthHeaders;
}
private static int CalculateTotalMonths(DateTime lowDateTime, DateTime highDateTime)
{
return CalculateMonthOffset(lowDateTime, highDateTime) + 1;
}
private static int CalculateMonthOffset(DateTime lowDateTime, DateTime highDateTime)
{
return (((highDateTime.Year - lowDateTime.Year) * 12) + highDateTime.Month - lowDateTime.Month);
}
}
}
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.API.Collections.RepoApi.Models.Reporting;
using LCU.Shared.Collections.RepoApiLib.Models.Reporting.Responses;
namespace LCU.Shared.Collections.RepoApiLib.Formatting
{
public static class DataTableFormatting
{
public static DataTable CreateDataTable<T>(IEnumerable<T> dataRows, Func<T, int> repoID, params Func<T, ReportHeaderCellMap>[] columns)
{
return new DataTable()
{
Headers = columns.Select(column => column(default(T)).Header).ToArray(),
Rows = dataRows.Select(entry => new RepoAccountReferenceRow()
{
RepoId = repoID(entry),
Cells = columns.Select(column => column(entry).Cell).ToArray()
})
};
}
}
}
@@ -0,0 +1,71 @@
using System;
using LCU.Shared.Collections.RepoApiLib.Models.Reporting.Responses;
namespace LCU.Shared.Collections.RepoApiLib.Formatting
{
public static class ReportCellFormatting
{
public static ReportCell CreateStringCell(string display)
{
return new ReportCell()
{
Display = string.IsNullOrEmpty(display) ? "-" : display
};
}
public static ReportCell CreateIntCell(int? number)
{
return new ReportCell()
{
Display = (number == null || number == 0) ? "-" : number?.ToString(),
Value = number
};
}
public static ReportCell CreateDecimalCell(decimal? number, bool displayZeroValue = false)
{
return new ReportCell()
{
Display = (number == null || (number == 0 && !displayZeroValue)) ? "-" : number?.ToString(),
Value = number
};
}
public static ReportCell CreatePercentageCell(decimal? number, bool isNormalized = false)
{
var display = "-";
if (number != null && number != 0)
{
if (!isNormalized)
{
number = number / 100;
}
display = string.Format(System.Globalization.CultureInfo.InvariantCulture,"{0:P2}", number);
}
return new ReportCell()
{
Display = display,
Value = number
};
}
public static ReportCell CreateDateCell(DateTime? dateTime)
{
return new ReportCell()
{
Display = dateTime?.ToString("MM/dd/yyyy") ?? "-",
Value = dateTime
};
}
public static ReportCell CreateCurrencyCell(decimal? amount)
{
return new ReportCell()
{
Display = (amount == null || amount == 0) ? "-" : string.Format("{0:C}", amount),
Value = amount
};
}
}
}
@@ -0,0 +1,334 @@
using System;
using LCU.API.Collections.RepoApi.Models.Reporting;
using LCU.Shared.Collections.RepoApiLib.Models.Reporting;
namespace LCU.Shared.Collections.RepoApiLib.Formatting
{
public static class StdReportEntryColumns
{
public static Func<StdReportEntry, ReportHeaderCellMap> Member(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Member",
Cell = ReportCellFormatting.CreateStringCell(entry?.IndividualName)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> AccountNo(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Account No",
Cell = ReportCellFormatting.CreateStringCell(entry?.AccountNo)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> AccountCode(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Account Code",
Cell = ReportCellFormatting.CreateStringCell(entry?.AccountTypeCode)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> Balance(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Balance",
Cell = ReportCellFormatting.CreateCurrencyCell(entry?.Balance)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> Status(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Status",
Cell = ReportCellFormatting.CreateStringCell(entry?.Status)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> DaysInStatus(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Days in Status",
Cell = ReportCellFormatting.CreateIntCell(entry?.DaysInStatus)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> Bankruptcy(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Bankruptcy",
Cell = ReportCellFormatting.CreateStringCell(entry?.Bankruptcy)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> Collector(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Collector",
Cell = ReportCellFormatting.CreateStringCell(entry?.Collector)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> DaysCurrentDelq(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Days Current Delq",
Cell = ReportCellFormatting.CreateIntCell(entry?.DaysCurrentDelq)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> DaysDelqWhenAdded(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Days Delq When Added",
Cell = ReportCellFormatting.CreateIntCell(entry?.DaysDelqWhenAdded)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> DelinquentAmount(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Delinquent Amount",
Cell = ReportCellFormatting.CreateCurrencyCell(entry?.DelinquentAmount)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> LastPaymentAmount(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Last Payment Amount",
Cell = ReportCellFormatting.CreateCurrencyCell(entry?.LastPaymentAmount)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> LastPaymentDate(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Last Payment Date",
Cell = ReportCellFormatting.CreateDateCell(entry?.LastPaymentDate)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> Auction(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Auction",
Cell = ReportCellFormatting.CreateStringCell(entry?.AuctionName)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> Collateral(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Collateral",
Cell = ReportCellFormatting.CreateStringCell($"{entry?.Year} {entry?.Make} {entry?.Model}")
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> RepoCompany(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Repo Company",
Cell = ReportCellFormatting.CreateStringCell(entry?.RepoCompany)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> RepoType(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Repo Type",
Cell = ReportCellFormatting.CreateStringCell(entry?.RepoType)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> DateAssigned(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Date Assigned",
Cell = ReportCellFormatting.CreateDateCell(entry?.RepoCompanyAssignmentDate)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> DateRepoed(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Date Repoed",
Cell = ReportCellFormatting.CreateDateCell(entry?.DateRepoed)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> RepoDuration(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Repo Duration",
Cell = ReportCellFormatting.CreateIntCell(entry?.RepoDuration)
};
}
// IMSAutoFile Data
public static Func<StdReportEntry, ReportHeaderCellMap> AutoImsStatus(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "AutoIMS Status",
Cell = ReportCellFormatting.CreateStringCell(entry?.AutoImsStatus)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> DateSold(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Date Sold",
Cell = ReportCellFormatting.CreateDateCell(entry?.SoldDate)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> DateEnteredAutoIMS(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Date Entered AutoIMS",
Cell = ReportCellFormatting.CreateDateCell(entry?.DateEnteredAutoIMS)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> ScheduledSaleDate(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Scheduled Sale Date",
Cell = ReportCellFormatting.CreateDateCell(entry?.ScheduledSaleDate)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> GrossPercentFloor(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Gross % Floor",
Cell = ReportCellFormatting.CreatePercentageCell(entry?.GrossPercentFloor)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> NetPercentFloor(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Net % Floor",
Cell = ReportCellFormatting.CreatePercentageCell(entry?.NetPercentFloor)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> TotalCharges(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Total Charges",
Cell = ReportCellFormatting.CreateCurrencyCell(entry?.TotalCharges)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> Grade(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Grade",
Cell = ReportCellFormatting.CreateDecimalCell(entry?.Grade, true)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> FloorPrice(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Floor Price",
Cell = ReportCellFormatting.CreateCurrencyCell(entry?.FloorPrice)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> GrossSalePrice(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Gross Sale Price",
Cell = ReportCellFormatting.CreateCurrencyCell(entry?.GrossSalePrice)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> FilteredStatus(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Filtered Status",
Cell = ReportCellFormatting.CreateStringCell(entry?.FilteredStatus)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> FilteredStatusChangeDate(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Filtered Status Changed On",
Cell = ReportCellFormatting.CreateDateCell(entry?.FilteredStatusChangeDate)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> O4RDate(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "First O4R",
Cell = ReportCellFormatting.CreateDateCell(entry?.O4RDate)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> SurplusDeficiency(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Surplus / Deficiency",
Cell = ReportCellFormatting.CreateCurrencyCell(entry?.GrossSalePrice - entry?.Balance)
};
}
public static Func<StdReportEntry, ReportHeaderCellMap> AutoIMSAssignmentToSold(string alternateHeader = null)
{
return (StdReportEntry entry) => new ReportHeaderCellMap()
{
Header = alternateHeader ?? "Days AutoIMS to Sold",
Cell = ReportCellFormatting.CreateIntCell(
entry != null ?
(
(entry.DateEnteredAutoIMS != null && entry.SoldDate != null) ?
(int?) (entry.SoldDate.Value - entry.DateEnteredAutoIMS.Value).TotalDays
: null
)
: null
)
};
}
}
}
@@ -0,0 +1,47 @@
using System.Threading;
using System.Threading.Tasks;
using LCU.API.Collections.RepoApi.Configuration;
using LCU.API.Collections.RepoApi.Services;
using LCU.Shared.Realtime.QueueLib.Models;
using LCU.Shared.Realtime.QueueLib.Queue;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
namespace LCU.API.Collections.RepoApi.HostedServices
{
public class HostedAutoImsTaskConsumer : BackgroundService, IQueueItemProcessor
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly IQueueConnectionConfig _configuration;
private readonly IQueueConsumerConfig _consumerConfig;
public HostedAutoImsTaskConsumer(IQueueConnectionConfig configuration, RepoApiConfiguration repoApiConfiguration, IServiceScopeFactory scopeFactory)
{
_configuration = configuration;
_scopeFactory = scopeFactory;
_consumerConfig = repoApiConfiguration.QueueConsumerConfigs["AutoImsTask"];
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
Log.Information("[QueueLib] Hosted Queue Producer is starting.");
var proxyQueue = new QueueItemConsumer(_configuration, _consumerConfig, this);
await proxyQueue.ProcessItems(cancellationToken, true, 0);
Log.Information("[QueueLib] Hosted Queue Producer is stopping.");
}
public void HandleItem(QueueItem item)
{
using (var scope = _scopeFactory.CreateScope())
{
var processor = scope.ServiceProvider.GetService<AutoImsService>();
var files = processor.Download();
processor.ProcessDownloadedFiles(files);
}
}
}
}
@@ -0,0 +1,60 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using LCU.API.Collections.RepoApi.Configuration;
using LCU.Shared.Collections.RepoApiLib.Hosted;
using LCU.Shared.Realtime.QueueLib.Models;
using LCU.Shared.Realtime.QueueLib.Queue;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Serilog;
namespace LCU.API.Collections.RepoApi.HostedServices
{
public class HostedRepoTaskConsumer : BackgroundService, IQueueItemProcessor
{
private readonly IServiceScopeFactory _scopeFactory;
private readonly IQueueConnectionConfig _configuration;
private readonly IQueueConsumerConfig _consumerConfig;
public HostedRepoTaskConsumer(IQueueConnectionConfig configuration, RepoApiConfiguration repoApiConfiguration, IServiceScopeFactory scopeFactory)
{
_configuration = configuration;
_scopeFactory = scopeFactory;
_consumerConfig = repoApiConfiguration.QueueConsumerConfigs["RepoProcessorTask"];
}
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
Log.Information("Hosted Queue Producer is starting.");
try
{
var proxyQueue = new QueueItemConsumer(_configuration, _consumerConfig, this);
await proxyQueue.ProcessItems(cancellationToken, true, 0);
}
catch (TaskCanceledException ex) when (cancellationToken.IsCancellationRequested)
{
Log.Information(ex, "Repo Producer was shut down normally.");
}
catch (Exception ex)
{
Log.Error(ex, "Error in main thread for HostedRepoTaskConsumer");
}
Log.Information("Hosted Repo Producer is stopping.");
}
public void HandleItem(QueueItem item)
{
var settings = new TaskSettings();
using (var scope = _scopeFactory.CreateScope())
{
var queueProducerService = scope.ServiceProvider.GetRequiredService<IRepoQueueProducerService>();
queueProducerService.ImportData(settings).GetAwaiter().GetResult();
}
}
}
}
@@ -0,0 +1,12 @@
using System.Threading.Tasks;
using LCU.Shared.Collections.RepoApiLib.Hosted;
using LCU.Shared.Collections.RepoApiLib.Models.Producer;
namespace LCU.API.Collections.RepoApi.HostedServices
{
public interface IRepoQueueProducerService
{
Task ImportData(TaskSettings settings);
void SendRequestToQueue(ProcessingItem queueRequest);
}
}
@@ -0,0 +1,319 @@
using System;
using System.Collections.Concurrent;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using LCU.API.Collections.RepoApi.Configuration;
using LCU.API.Collections.RepoApi.Exceptions;
using LCU.API.Collections.RepoApi.Services;
using LCU.API.Collections.RepoApi.Services.Producer;
using LCU.Api.Internal.MaidBotLib.Models;
using LCU.Shared.Collections.RepoApiLib.Models;
using LCU.Shared.Collections.RepoApiLib.Models.Consumer;
using LCU.Shared.Collections.RepoApiLib.Models.Producer;
using LCU.Shared.Collections.RepoApiLib.Repositories;
using LCU.Shared.Realtime.QueueLib.Models;
using LCU.Shared.Realtime.QueueLib.Queue;
using Microsoft.CodeAnalysis.Operations;
using Microsoft.EntityFrameworkCore.Internal;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Newtonsoft.Json;
using Serilog;
namespace LCU.API.Collections.RepoApi.HostedServices
{
/**
* Special Notes.
* 1. Direct injection of services cannot be done, the main API context is on it's own
* scope/thread. So instead, the scope factory is passed, so that this can create scopes.
* 2. This is designed to use 5 threads, from within an IHostedService thread context, to
* process queues. It's designed to do this in 'batches' to simulate a batch job, which
* is where the creation of the RunInfo comes into scope.
* 3. There is a main 'thread' here, which waits for a 'batch' of work to be complete.
* 4. Under the main thread, there are 5 worker threads, communicating using two queues besides
* the main Rabbit queue. The queues are used to set something up for processing (_inputQueue)
* and to mark something as done processing (_outputQueue).
*/
public class RepoQueueConsumerService : BackgroundService, IQueueItemProcessor
{
private readonly IQueueConnectionConfig _connectionConfig;
private readonly IServiceScopeFactory _scopeFactory;
private RepoRunInfo _runInfo;
private const int ThreadCount = 5;
private BlockingCollection<QueueItem> _inputQueue;
private BlockingCollection<QueueResult> _outputQueue;
private readonly ConcurrentDictionary<string, ProcessingItem> _inProgress;
private readonly Task[] _workers;
private Task _consumer;
private QueueItemConsumer _queue;
private readonly IQueueConsumerConfig _consumerConfig;
private CancellationTokenSource _individualRunToken;
public RepoQueueConsumerService(IQueueConnectionConfig connectionConfig, RepoApiConfiguration repoApiConfiguration, IServiceScopeFactory scopeFactory)
{
_connectionConfig = connectionConfig;
_scopeFactory = scopeFactory;
_inProgress = new ConcurrentDictionary<string, ProcessingItem>();
_workers = new Task[ThreadCount];
_consumerConfig = repoApiConfiguration.QueueConsumerConfigs["RepoAccountQueue"];
}
bool TopLevelThreadController()
{
Log.Information($"[Startup] {_consumerConfig.GetQueueName()} Consumer is starting a run.");
_runInfo = new RepoRunInfo();
foreach (var workItem in _outputQueue.GetConsumingEnumerable())
{
_queue.AckItem(workItem.DeliveryTag);
if (workItem.Receipt != null)
{
_runInfo.Receipts.Add(workItem.Receipt);
}
var remainingCount = _queue.CheckQueueCount();
// First the input queue will finish, and we have to let running tasks finish.
if (remainingCount == 0 && EnumerableExtensions.Any(_inputQueue) == false && !_inputQueue.IsCompleted)
{
_inputQueue.CompleteAdding();
Task.WaitAll(_workers);
}
// That will cause the output queue to have the last THREADCOUNT items added, but
// we are IN the loop that processes them, so wen can't wait, just continue...
if (remainingCount == 0 && _inputQueue.IsCompleted && !_outputQueue.IsAddingCompleted)
{
_outputQueue.CompleteAdding();
}
// Ok, both queues are complete, which means all done.
if (_inputQueue.IsCompleted && _outputQueue.IsCompleted)
{
_individualRunToken.Cancel();
_inputQueue.Dispose();
_outputQueue.Dispose();
Log.Information($"{_consumerConfig.GetQueueName()} run completed... recording RunInfo: {@_runInfo}", _runInfo);
_runInfo.CompleteDateTime = DateTime.Now;
SaveRunInfo(_runInfo);
SendErrorNotification(_runInfo);
SendAlertNotification(_runInfo);
return true;
}
}
Log.Error($"[Startup] {_consumerConfig.GetQueueName()} Consumer is finished in an unexpected way. {@_runInfo}", _runInfo);
return false;
}
private void SaveRunInfo(RepoRunInfo runInfo)
{
using (var scope = _scopeFactory.CreateScope())
{
try
{
var receiptService = scope.ServiceProvider.GetRequiredService<IProcessingReceiptService>();
receiptService.SaveAllAsync(runInfo.Receipts).GetAwaiter().GetResult();
}
catch (Exception ex)
{
Log.Error(ex, "Could not save repo processor results.");
}
}
}
private void SendErrorNotification(RepoRunInfo runInfo)
{
if (runInfo.Receipts.Count > 0)
{
using (var scope = _scopeFactory.CreateScope())
{
var resp = scope.ServiceProvider.GetService<IEmailer>().SendProccessSummary(_runInfo);
if (!resp.Result.IsSuccessStatusCode)
{
Log.Error(
$"RepoQueueConsumerService failed to send error email. Emailer Response: {resp.Result.StatusCode} - {resp.Result.ReasonPhrase}");
}
}
}
}
private void SendAlertNotification(RepoRunInfo runInfo)
{
if (runInfo.Receipts.Count > 0)
{
using (var scope = _scopeFactory.CreateScope())
{
var resp = scope.ServiceProvider.GetService<IEmailer>().SendAlertSummaryEmailWithEndTime(runInfo.CompleteDateTime?? DateTime.Now);
if (!resp.Result.IsSuccessStatusCode)
{
Log.Error(
$"RepoQueueConsumerService failed to send alerts email. Emailer Response: {resp.Result.StatusCode} - {resp.Result.ReasonPhrase}");
}
}
}
}
private void SingleThreadMessageWorker(int workerId)
{
Log.Debug("[Startup] Worker {0} is starting.", workerId);
foreach (var workItem in _inputQueue.GetConsumingEnumerable())
{
var message = Encoding.UTF8.GetString(workItem.Body.ToArray());
Log.Information("Worker {0} Processing {1}", workerId, message);
var result = HandleSingleItem(message).GetAwaiter().GetResult();
result.DeliveryTag = workItem.DeliveryTag;
_outputQueue.Add(result);
}
Log.Debug("[Shutdown] Worker {0} is complete.", workerId);
}
/**
* This function is the main function run by the IHostedService interface, so
* should be considered the 'main' here.
*/
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (_queue == null)
{
try
{
_queue = new QueueItemConsumer(_connectionConfig, _consumerConfig, this);
}
catch (Exception ex)
{
Log.Error(ex, "Failed to create RepoQueueConsumerService, Will try again in 1 minute.");
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
}
try
{
while (!stoppingToken.IsCancellationRequested)
{
Log.Debug($"{_consumerConfig.GetQueueName()} Processor started a run.");
_inputQueue = new BlockingCollection<QueueItem>();
_outputQueue = new BlockingCollection<QueueResult>();
_consumer = Task<bool>.Factory.StartNew(TopLevelThreadController, stoppingToken);
for (var threadIndex = 0; threadIndex < ThreadCount; ++threadIndex)
{
var workerId = threadIndex;
var task = new Task(() => SingleThreadMessageWorker(workerId));
_workers[threadIndex] = task;
task.Start();
}
_individualRunToken = new CancellationTokenSource();
await _queue.ProcessItems(_individualRunToken.Token, false, ThreadCount);
while (!_consumer.IsCompleted)
{
await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken);
Log.Debug($"{_consumerConfig.GetQueueName()} Finishing Heartbeat.");
}
_consumer?.Dispose();
_individualRunToken.Dispose();
Log.Debug($"{_consumerConfig.GetQueueName()} Processor Completed a run.");
await Task.Delay(TimeSpan.FromMinutes(1), stoppingToken);
}
// the thread is cancelled, so the loop will exit, be sure to propagate the cancellation to our token
if (_individualRunToken != null && _individualRunToken.IsCancellationRequested == false)
{
_individualRunToken.Cancel();
}
}
catch (TaskCanceledException ex) when (stoppingToken.IsCancellationRequested)
{
Log.Information(ex, "Repo Processor was shut down normally.");
}
catch (Exception ex)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Fatal(ex,$"There is an unhandled exception when processing repos; The IIS App Pool may need to be restarted.");
}
}
private async Task<QueueResult> HandleSingleItem(string itemAsString)
{
var result = new QueueResult();
ProcessingItem item = null;
try
{
using (var scope = _scopeFactory.CreateScope())
{
var repoAccountProcessor = scope.ServiceProvider.GetRequiredService<IRepoAccountProcessor>();
item = JsonConvert.DeserializeObject<ProcessingItem>(itemAsString);
Log.Debug($"{_consumerConfig.GetQueueName()} Consumer received: {@item}", item);
var newKey = item.DetermineAuditItemKey();
if (_inProgress.TryAdd(newKey, item) == false)
{
Log.Information("Conflicting item found for {0}, skipping.",
item.DetermineAuditItemKey(),
_inProgress[newKey].DetermineAuditItemKey());
return result;
}
var receipt = new ProcessingReceipt(item.AccountNo)
{
AccountId = item.AccountId ?? "",
CaseId = item.CaseId ?? "",
QueuedBy = item.QueuedBy
};
try
{
Log.Information($"Processing {item.AccountNo}");
receipt.Outcome = await repoAccountProcessor.HandleAccount(item);
receipt.Message = "Success";
}
catch (IneligibleAccountException iaEx)
{
receipt.Message = $"The item {item.AccountNo} is ineligible {iaEx.Field} : {iaEx.Reason}";
receipt.Outcome = RepoProcessingResult.Ineligible;
}
catch (MissingRequirementException mrEx)
{
receipt.Message = $"The item {item.AccountNo} is missing {mrEx.Field} : {mrEx.Advice}.";
receipt.Outcome = RepoProcessingResult.MissingRequirement;
}
catch (Exception ex) /* Mostly ServerErrorException */
{
receipt.Message = $"The item {item.AccountNo} produced an error. Please investigate.";
Log.Error(ex, receipt.Message);
}
if (!_inProgress.TryRemove(newKey, out item))
{
Log.Error("Couldn't remove item from concurrent dictionary. (Success branch) {@item}", item);
}
result.Receipt = receipt;
return result;
}
}
catch (Exception ex)
{
Log.Fatal(ex, $"[INVESTIGATE] Failed processing {itemAsString}.");
if (item == null) return result;
var newKey = item.DetermineAuditItemKey();
if (!_inProgress.TryRemove(newKey, out item))
{
Log.Error("Couldn't remove item from concurrent dictionary. (Exception branch) {@item}", item);
}
return result;
}
}
public void HandleItem(QueueItem item)
{
_inputQueue.Add(item);
}
}
}
@@ -0,0 +1,140 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using LCU.API.Collections.RepoApi.Configuration;
using LCU.API.Collections.RepoApi.DataAccess;
using LCU.API.Collections.RepoApi.Services;
using LCU.Shared.Collections.RepoApiLib.Hosted;
using LCU.Shared.Collections.RepoApiLib.Models;
using LCU.Shared.Collections.RepoApiLib.Models.Producer;
using LCU.Shared.Collections.RepoApiLib.Repositories;
using LCU.Shared.Realtime.QueueLib.Models;
using LCU.Shared.Realtime.QueueLib.Queue;
using Serilog;
namespace LCU.API.Collections.RepoApi.HostedServices
{
public class RepoQueueProducerService : IRepoQueueProducerService
{
private readonly RepoApiConfiguration _configuration;
private readonly ISettingsService _settingsService;
private readonly ICollectionsDao _collectionsDao;
private readonly QueueItemProducer<ProcessingItem> _queueService;
private readonly IQueueConsumerConfig _consumerConfig;
private readonly IRepoAccountService _repoAccountService;
private readonly IProcessingReceiptService _processingReceiptService;
public RepoQueueProducerService(IQueueConnectionConfig connectionConfig,
IRepoAccountService repoAcountService,
RepoApiConfiguration configuration,
ISettingsService settingsService,
ICollectionsDao collectionsDao,
IProcessingReceiptService processingReceiptService)
{
_configuration = configuration;
_settingsService = settingsService;
_collectionsDao = collectionsDao;
_processingReceiptService = processingReceiptService;
_consumerConfig = configuration.QueueConsumerConfigs["RepoAccountQueue"];
_queueService = new QueueItemProducer<ProcessingItem>(connectionConfig);
_repoAccountService = repoAcountService;
}
public async Task ImportData(TaskSettings settings)
{
try
{
var reprocessItems = await _processingReceiptService.GetRetryListAsync();
var watchStatusItems = await RetrieveRepoAccountsOfInterest();
var combinedList = CreateCombinedList(reprocessItems, watchStatusItems);
Log.Information($"Repo Producer Queueing {combinedList.Count} results.");
if (combinedList.Any())
{
_queueService.EnqueueList(combinedList, _consumerConfig);
}
}
catch (Exception ex)
{
Log.Fatal(ex, "Error Deserializing. Repo Producer Failed to queue accounts.");
}
try
{
var fullConfig = await _collectionsDao.RetrieveTemenosConfigurationAsync();
var users = fullConfig.Users.Select(x => new ExternalUser()
{
Code = x.Code,
ReferenceId = x.Id,
Name = x.Description
}).ToList();
await _settingsService.SyncExternalUsersAsync(users);
}
catch (Exception ex)
{
Log.Fatal(ex, "Error Updating external user list from Temenos.");
}
}
public IReadOnlyCollection<ProcessingItem> CreateCombinedList(IReadOnlyCollection<ProcessingReceipt> listPriority,
IReadOnlyCollection<ProcessingItem> listSecondary)
{
var combinedList = new List<ProcessingItem>();
if (listPriority != null && listPriority.Any())
{
foreach (var processingItem in listPriority)
{
combinedList.Add(new ProcessingItem()
{
AccountNo = processingItem.AccountNo,
AccountId = processingItem.AccountId,
CaseId = processingItem.CaseId,
QueuedBy = "RepoManager-Retry"
});
}
}
if (listSecondary == null || !listSecondary.Any()) return combinedList;
foreach (var processingItem in listSecondary)
{
if (combinedList.Any(x => x.AccountNo == processingItem.AccountNo)) continue;
processingItem.QueuedBy = "RepoManager";
combinedList.Add(processingItem);
}
return combinedList;
}
public async Task<IReadOnlyCollection<ProcessingItem>> RetrieveRepoAccountsOfInterest()
{
var repoAccountNos = new List<ProcessingItem>();
try
{
repoAccountNos.AddRange(await _repoAccountService.GetItemsForProcessing());
}
catch (Exception ex)
{
Log.Error(ex, "Failed to Retrieve Repo Accounts For Polling in RepoQueueProducerService");
}
return repoAccountNos;
}
public void SendRequestToQueue(ProcessingItem queueRequest)
{
try
{
_queueService.Enqueue(queueRequest, _consumerConfig);
Log.Debug($"Queued The Following Account {queueRequest.AccountNo}");
}
catch (Exception ex)
{
Log.Error(ex, $"Failed to Add AccountNo To Queue {queueRequest.AccountNo}");
}
}
}
}
@@ -0,0 +1,55 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>netcoreapp2.2</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile>C:\dev\projects\LCU.API.Collections.RepoApi\LCU.API.Collections.RepoApi\LCU.API.Collections.RepoApi.xml</DocumentationFile>
<WarningsAsErrors>NU1605</WarningsAsErrors>
<NoWarn>1701;1702;1591</NoWarn>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<DocumentationFile>C:\dev\projects\LCU.API.Collections.RepoApi\LCU.API.Collections.RepoApi\LCU.API.Collections.RepoApi.xml</DocumentationFile>
<NoWarn>1701;1702;1591</NoWarn>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CsvTextFieldParser" Version="1.2.1" />
<PackageReference Include="LCU.API.Collections.CollectionsApiLib" Version="1.11.0" />
<PackageReference Include="LCU.Api.Internal.MaidBotLib" Version="1.0.0" />
<PackageReference Include="LCU.Shared.Common.NotificationApiLib" Version="2.0.0" />
<PackageReference Include="LCU.Shared.Realtime.QueueLib" Version="2.1.0" />
<PackageReference Include="Microsoft.AspNetCore.App" />
<PackageReference Include="Microsoft.AspNetCore.Razor.Design" Version="2.2.0" PrivateAssets="All" />
<PackageReference Include="Microsoft.Azure.AppConfiguration.AspNetCore" Version="4.2.0" />
<PackageReference Include="Microsoft.FeatureManagement" Version="2.2.0" />
<PackageReference Include="Microsoft.FeatureManagement.AspNetCore" Version="2.2.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.2.3" />
<PackageReference Include="ncrontab" Version="3.3.1" />
<PackageReference Include="Serilog" Version="2.9.0" />
<PackageReference Include="Serilog.AspNetCore" Version="3.2.0" />
<PackageReference Include="Serilog.Sinks.Async" Version="1.4.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="3.1.1" />
<PackageReference Include="Serilog.Sinks.Seq" Version="4.0.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="4.0.1" />
<PackageReference Include="WinSCP" Version="5.15.3" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\LCU.Shared.Collections.RepoApiLib\LCU.Shared.Collections.RepoApiLib.csproj" />
</ItemGroup>
<ItemGroup>
<Folder Include="Services\AutoIms\" />
</ItemGroup>
<ItemGroup>
<None Update="privatekey.ppk">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
@@ -0,0 +1,127 @@
<?xml version="1.0"?>
<doc>
<assembly>
<name>LCU.API.Collections.RepoApi</name>
</assembly>
<members>
<member name="T:LCU.API.Collections.RepoApi.AlertChecker.TimeInStatusChecker">
This class can be used to create the alerts in the "Days + ' days in REHO. '" series. for both REHO and O4R
</member>
<member name="M:LCU.API.Collections.RepoApi.Controllers.LoggingController.Post(LCU.Shared.Collections.RepoApiLib.Models.ClientError)">
<summary>
Use to send something to the logger from the client
</summary>
<remarks>
Logs a message from a client.
Cookies: None required or set.
Security: Must have a valid JWT token retrieved from Azure AD, with ADMIN role.
</remarks>
</member>
<member name="T:LCU.API.Collections.RepoApi.Domain.FloorPricing.Strategies.AutoImsSuggestedValue">
This strategy uses the suggested price from the ImsAutoFileData entry for the auction.
</member>
<member name="T:LCU.API.Collections.RepoApi.Domain.FloorPricing.Strategies.NadaMmrConditionValue">
This strategy uses the AutoIms MMR values, the values from Nada, and the condition from AutoIms along
with a specific formula, to calculate a floor.
</member>
<member name="T:LCU.API.Collections.RepoApi.Exceptions.NotFoundException">
If an entity cannot be found, that specific error can allow for a 404 or 400 with more detail to be
returned. So specify that to the API layer.
</member>
<member name="T:LCU.API.Collections.RepoApi.Exceptions.ServerErrorException">
Do not want to expose database and related exceptions to the controllers / web layer.
The lower layers log what happened for developers, then return a general error; unless
a more specific error can be returned in a way to allow the client to act (such as NotFound).
</member>
<member name="T:LCU.API.Collections.RepoApi.HostedServices.RepoQueueConsumerService">
Special Notes.
1. Direct injection of services cannot be done, the main API context is on it's own
scope/thread. So instead, the scope factory is passed, so that this can create scopes.
2. This is designed to use 5 threads, from within an IHostedService thread context, to
process queues. It's designed to do this in 'batches' to simulate a batch job, which
is where the creation of the RunInfo comes into scope.
3. There is a main 'thread' here, which waits for a 'batch' of work to be complete.
4. Under the main thread, there are 5 worker threads, communicating using two queues besides
the main Rabbit queue. The queues are used to set something up for processing (_inputQueue)
and to mark something as done processing (_outputQueue).
</member>
<member name="M:LCU.API.Collections.RepoApi.HostedServices.RepoQueueConsumerService.ExecuteAsync(System.Threading.CancellationToken)">
This function is the main function run by the IHostedService interface, so
should be considered the 'main' here.
</member>
<member name="M:LCU.API.Collections.RepoApi.Services.AuctionAssignmentService.CreateAutoIMSAuction(LCU.Shared.Collections.RepoApiLib.Models.Collateral,LCU.Shared.Collections.RepoApiLib.WebServices.AutoIMS.VehicleLocation,LCU.Shared.Collections.RepoApiLib.Models.AuctionAssignment)">
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.
</member>
<member name="M:LCU.API.Collections.RepoApi.Services.AuctionAssignmentService.RemoveAuctionFromAutoIMS(LCU.Shared.Collections.RepoApiLib.Models.AuctionAssignment)">
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.
</member>
<member name="M:LCU.API.Collections.RepoApi.Services.AuctionAssignmentService.UpdateAuctionFloor(LCU.Shared.Collections.RepoApiLib.Models.AuctionAssignment,LCU.Shared.Collections.RepoApiLib.Models.AuctionAssignment)">
This function will attempt to call AutoIMS with an auction floor, and will not update the
floor related values unless it succeeds
</member>
<member name="M:LCU.API.Collections.RepoApi.Services.AuctionAssignmentService.UpdateTitleSentDate(LCU.Shared.Collections.RepoApiLib.Models.AuctionAssignment,LCU.Shared.Collections.RepoApiLib.Models.AuctionAssignment)">
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
</member>
<member name="M:LCU.API.Collections.RepoApi.Services.AuctionAssignmentService.UpdateRedeemedDate(LCU.Shared.Collections.RepoApiLib.Models.AuctionAssignment,LCU.Shared.Collections.RepoApiLib.Models.AuctionAssignment)">
This function will attempt to call AutoIMS and set the redeemed date, and will not update the
redeemed date unless it succeeds
</member>
<member name="M:LCU.API.Collections.RepoApi.Services.AuctionAssignmentService.UpdateHoldData(LCU.Shared.Collections.RepoApiLib.Models.AuctionAssignment,LCU.Shared.Collections.RepoApiLib.Models.AuctionAssignment)">
This function will attempt to call AutoIMS to place a hold, and will not update the
hold related values unless it succeeds
</member>
<member name="M:LCU.API.Collections.RepoApi.Services.AutoIms.AutoImsImporterService.ImportNotesToDatabase(System.Collections.Generic.IEnumerable{LCU.Shared.Collections.RepoApiLib.Models.AuctionAssignmentNote})">
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.
</member>
<member name="M:LCU.API.Collections.RepoApi.Services.AutoIms.AutoImsImporterService.ImportDataFile(System.Collections.Generic.IEnumerable{LCU.Shared.Collections.RepoApiLib.Models.ImsAutoFileData})">
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.
</member>
<member name="M:LCU.API.Collections.RepoApi.Services.AutoIms.ImsAutoFileNoteConverter.CompressIdentifyingRows(System.Collections.Generic.IEnumerable{LCU.Shared.Collections.RepoApiLib.Models.AuctionAssignmentNote})">
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.
</member>
<member name="M:LCU.API.Collections.RepoApi.Services.Producer.IAccountAlertCheckService.AlertChecksPreStatusReject(LCU.Shared.Collections.RepoApiLib.Models.RepoAccount,LCU.Shared.Collections.RepoApiLib.Models.RepoAccount)">
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?
</member>
<member name="M:LCU.API.Collections.RepoApi.Services.Producer.IAccountAlertCheckService.AlertChecksPrimary(LCU.Shared.Collections.RepoApiLib.Models.RepoAccount,LCU.Shared.Collections.RepoApiLib.Models.RepoAccount)">
These alerts run after the account has been updated or created and is the primary set of alert checks.
</member>
<member name="M:LCU.API.Collections.RepoApi.Services.Producer.RepoAccountProcessorService.ConvertBorrowerType(System.String)">
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...
</member>
<member name="M:LCU.API.Collections.RepoApi.Services.Producer.RepoAccountProcessorService.UpdateFieldsOnStatusChange(LCU.Shared.Collections.RepoApiLib.Models.RepoAccount,LCU.Shared.Collections.RepoApiLib.Models.RepoAccount)">
If the status changed to 04R from not-O4R.
Is there a repo company assignment (even the default), if not assign one.
If there if a repo company assignment, but the status has changed, forcibly re-assign default
</member>
<member name="M:LCU.API.Collections.RepoApi.Services.Producer.RepoAccountProcessorService.SetNewO4RDataForAccount(LCU.Shared.Collections.RepoApiLib.Models.RepoAccount,LCU.Shared.Collections.RepoApiLib.Models.Collateral)">
When an account changes to 04R, update all relevant fields/dates.
</member>
<member name="M:LCU.API.Collections.RepoApi.Services.Producer.RepoAccountProcessorService.UpdateCollateralStatus(LCU.Shared.Collections.RepoApiLib.Models.RepoAccount,LCU.Shared.Collections.RepoApiLib.Models.RepoAccount)">
if the status was O4R and is now REPO, update date hooked (picked up).
</member>
<member name="M:LCU.API.Collections.RepoApi.Services.RepoAccountService.SelectRepoAccount(System.Collections.Generic.IReadOnlyCollection{LCU.Shared.Collections.RepoApiLib.Models.RepoAccount})">
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.
</member>
<member name="M:LCU.API.Collections.RepoApi.Services.StatusService.ConvertStatus(LCU.API.Collections.CollectionsApiLib.Models.RepoStatus)">
TODO #7712
Convert a Stage from a case to a RepoAccount status.
</member>
</members>
</doc>
@@ -0,0 +1,113 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading.Tasks;
using LCU.Shared.Collections.RepoApiLib.WebServices.AutoIMS;
namespace LCU.API.Collections.RepoApi.MockServices
{
// Excluded from code coverage because this service is a mock.
[ExcludeFromCodeCoverage]
public class MockAutoImsWebService : IAutoImsWebService
{
public async Task<WebServiceResponse> AddAuctionNote(string vin, string customerNumber, string note, string writer)
{
return await Task.FromResult(new WebServiceResponse()
{
Status = new WebServiceResponseStatus()
{
Code = 1,
Description = "Auction note added"
}
});
}
public string GetCollateralImage(string conditionReporUrl)
{
return "";
}
public async Task<WebServiceResponse> PlaceHold(string VIN, int holdCode, DateTime holdUntil)
{
return await Task.FromResult(new WebServiceResponse()
{
Status = new WebServiceResponseStatus()
{
Code = 1,
Description = "Vehicle placed on hold"
}
});
}
public async Task<WebServiceResponse> PostVehicleAsync<T>(T vehicle)
{
return await Task.FromResult(new WebServiceResponse()
{
Status = new WebServiceResponseStatus()
{
Code = 1,
Description = "Vehicle posted to auction"
}
});
}
public async Task<WebServiceResponse> Redeem(string VIN)
{
return await Task.FromResult(new WebServiceResponse()
{
Status = new WebServiceResponseStatus()
{
Code = 1,
Description = "Vehicle set to redeemed"
}
});
}
public async Task<WebServiceResponse> RemoveAuction(string VIN, string customerNumber)
{
return await Task.FromResult(new WebServiceResponse()
{
Status = new WebServiceResponseStatus()
{
Code = 1,
Description = "Auction removed"
}
});
}
public async Task<WebServiceResponse> RemoveFromAuction(string vin, string clientStockNumber)
{
return await Task.FromResult(new WebServiceResponse()
{
Status = new WebServiceResponseStatus()
{
Code = 1,
Description = "Vehicle removed from auction"
}
});
}
public async Task<WebServiceResponse> TitleSent(string VIN)
{
return await Task.FromResult(new WebServiceResponse()
{
Status = new WebServiceResponseStatus()
{
Code = 1,
Description = "Floor price updated"
}
});
}
public async Task<WebServiceResponse> UpdateAuctionIdentifyingValuesAsync(string customerNum, string vin, string auctionAssignmentId)
{
return await Task.FromResult(new WebServiceResponse()
{
Status = new WebServiceResponseStatus()
{
Code = 1,
Description = "Identifying values updated"
}
});
}
}
}
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace LCU.API.Collections.RepoApi.Models
{
public class AppCorsConfig
{
public string[] CORS_AllowedOrigins { get; set; }
}
}
@@ -0,0 +1,10 @@
namespace LCU.API.Collections.RepoApi.Models
{
public class AppUser
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Email { get; set; }
public string[] Roles { get; set; }
}
}
@@ -0,0 +1,25 @@
using Microsoft.AspNetCore.Mvc;
using Newtonsoft.Json;
namespace LCU.API.Collections.RepoApi.Models.Filters
{
public class AuctionAssignmentFilter : PageFilter
{
[FromQuery(Name = "readyForFloorPrice")]
public bool ReadyForFloorPrice { get; set; }
public AuctionAssignmentFilter() : base()
{
this.ReadyForFloorPrice = false;
}
public override object Clone()
{
var jsonString = JsonConvert.SerializeObject(this);
return JsonConvert.DeserializeObject(jsonString, this.GetType());
}
}
}
@@ -0,0 +1,27 @@
using System;
using System.ComponentModel.DataAnnotations;
using Newtonsoft.Json;
namespace LCU.API.Collections.RepoApi.Models.Filters
{
public class PageFilter : ICloneable
{
[Range(1, 1000)]
public int Page { get; set; }
[Range(1, 1000)]
public int Limit { get; set; }
public PageFilter()
{
this.Page = 1;
this.Limit = 100;
}
public virtual object Clone()
{
var jsonString = JsonConvert.SerializeObject(this);
return JsonConvert.DeserializeObject(jsonString, this.GetType());
}
}
}
@@ -0,0 +1,10 @@
using LCU.Shared.Collections.RepoApiLib.Models.Reporting.Responses;
namespace LCU.API.Collections.RepoApi.Models.Reporting
{
public class ReportHeaderCellMap
{
public string Header { get; set; }
public ReportCell Cell { get; set; }
}
}
@@ -0,0 +1,29 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace LCU.API.Collections.RepoApi.Models.Requests
{
public class AccountQueueRequest : IValidatableObject
{
[Required]
[StringLength(16, MinimumLength = 7,
ErrorMessage = "The AccountNo field must have a length between 7 and 16 characters")]
public string AccountNo { get; set; }
[StringLength(20, MinimumLength = 1)] public string CaseId { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var results = new List<ValidationResult>();
if (string.IsNullOrWhiteSpace(AccountNo))
{
results.Add(new ValidationResult(
"AccountNo (AccountIdentifier) is not valid",
new[] { nameof(AccountNo) }));
}
return results;
}
}
}
@@ -0,0 +1,41 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using LCU.Shared.Collections.RepoApiLib.Models;
using LCU.Shared.Collections.RepoApiLib.WebServices.AutoIMS;
namespace LCU.API.Collections.RepoApi.Models.Requests
{
public class AuctionAssignmentPostRequest : IValidatableObject
{
[Required]
public Shared.Collections.RepoApiLib.Models.AuctionAssignment AuctionAssignment { get; set; }
[Required]
public IReadOnlyCollection<Collateral> Collaterals { get; set; }
[Required]
public VehicleLocation VehicleLocation { get; set; }
[Required]
public int? PrimaryCollateralId { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
var failedValidations = new List<ValidationResult>();
if (this.Collaterals.Count == 0)
{
failedValidations.Add(new ValidationResult("There must be at least one collateral when creating an auction assignment."));
}
var primaryCollateral = Collaterals.Where(collateral => collateral.CollateralId == PrimaryCollateralId).FirstOrDefault();
if (primaryCollateral == null)
{
failedValidations.Add(new ValidationResult("Collaterals must contain a piece of Collateral with a CollateralID that matches the PrimaryCollateralId."));
}
return failedValidations;
}
}
}
@@ -0,0 +1,17 @@
using System.Collections.Generic;
namespace LCU.API.Collections.RepoApi.Models.Requests
{
public class RepoAccountPostFilter
{
public List<int?> RepoIdList { get; set; }
public string Status { get; set; }
public string AuctionHouseId { get; set; }
public int? RepoCompanyId { get; set; }
public bool? OnlyOpenAccounts { get; set; }
}
}
@@ -0,0 +1,16 @@
using System;
namespace LCU.API.Collections.RepoApi.Models.Requests
{
public class ReportingRequest
{
public DateTime LowDate { get; set; }
public DateTime HighDate { get; set; }
public ReportingRequest()
{
HighDate = DateTime.Today;
LowDate = HighDate.Subtract(TimeSpan.FromDays(365));
}
}
}
@@ -0,0 +1,9 @@
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Models.Requests
{
public class VehicleFloorPricingParametersPutRequest
{
public VehicleFloorPricingParameters[] vehicleFloorPricingParameters;
}
}
@@ -0,0 +1,14 @@
using System.Collections.Generic;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Models.Responses
{
public class FactoidResponse
{
public string CumulativeValue { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public string FontAwesomeIcon { get; set; }
public IReadOnlyCollection<RepoAccount> Records { get; set; }
}
}
@@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using LCU.API.Collections.RepoApi.Models.Filters;
using LCU.Shared.Collections.RepoApiLib.Models.Results;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
namespace LCU.API.Collections.RepoApi.Models.Responses
{
public class PagedResponse<T> where T : class
{
public PagedResponse() { }
public PagedResponse(PagedResult<T> pagedResult, PageFilter filter, IUrlHelper url, HttpRequest request, string uriAction = "Get")
{
TotalItems = pagedResult.Total;
Items = pagedResult.Items;
CurrentPage = filter.Page;
TotalPages = pagedResult.Total == 0 ? 1 : (int) Math.Ceiling((decimal) pagedResult.Total / filter.Limit);
PageFilter newFilter = filter.Clone() as PageFilter;
if (pagedResult.Total > (filter.Page * filter.Limit))
{
newFilter.Page = filter.Page + 1;
NextPage = new Uri(url.Action(uriAction, null, newFilter, request.Scheme));
}
if (filter.Page > 1)
{
newFilter.Page = filter.Page - 1;
PreviousPage = new Uri(url.Action(uriAction, null, newFilter, request.Scheme));
}
}
public IEnumerable<T> Items { get; set; }
public Uri NextPage { get; set; }
public Uri PreviousPage { get; set; }
public int TotalItems { get; set; }
public int CurrentPage { get; set; }
public int TotalPages { get; set; }
}
}
@@ -0,0 +1,10 @@
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Models.Responses
{
public class RepoCompanySelectionPostResponse
{
public RepoCompanySelection PreviousRepoCompanySelection {get; set;}
public RepoCompanySelection NewRepoCompanySelection { get; set; }
}
}
@@ -0,0 +1,9 @@
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Models.Responses
{
public class VehicleFloorPricingParametersPutResponse
{
public VehicleFloorPricingParameters[] vehicleFloorPricingParameters;
}
}
@@ -0,0 +1,11 @@
using System;
namespace LCU.API.Collections.RepoApi.Models.Results
{
public class DateTimeResult : IResult<DateTime?>
{
public DateTime? Value { get; set; } = null;
public string ErrorMessage { get; set; } = null;
public bool IsSuccess { get; set; } = false;
}
}
@@ -0,0 +1,11 @@
using System;
namespace LCU.API.Collections.RepoApi.Models.Results
{
public interface IResult<T>
{
T Value { get; set; }
bool IsSuccess { get; set; }
String ErrorMessage { get; set; }
}
}
@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace LCU.API.Collections.RepoApi.Models
{
public class SqlOptionsConfig
{
public int MaxRetryCount { get; set; }
public TimeSpan MaxRetryDelay { get; set; }
public List<int> ErrorNumbersToAdd { get; set; }
public bool EnableDetailedErrors { get; set; }
}
}
@@ -0,0 +1,21 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Serilog;
namespace LCU.API.Collections.RepoApi
{
[ExcludeFromCodeCoverage]
public class Program
{
public static void Main(string[] args)
{
CreateWebHostBuilder(args).Build().Run();
}
public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
WebHost.CreateDefaultBuilder(args)
.UseStartup<Startup>().UseSerilog((hostingContext, loggerConfiguration) => loggerConfiguration
.ReadFrom.Configuration(hostingContext.Configuration));
}
}
@@ -0,0 +1,30 @@
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:22532",
"sslPort": 44380
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"LCU.API.Collections.RepoApi": {
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
}
}
}
@@ -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;
}
}
}

Some files were not shown because too many files have changed in this diff Show More