Adding
This commit is contained in:
+128
@@ -0,0 +1,128 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.CollectionsApiLib.Models;
|
||||
using LCU.API.Collections.RepoApi.Domain.AlertChecker;
|
||||
using LCU.API.Collections.RepoApi.Domain.Processing;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.Producer
|
||||
{
|
||||
public class AccountAlertCheckService : IAccountAlertCheckService
|
||||
{
|
||||
private const string O4RStatus = "O4R";
|
||||
private const string NoStatus = "";
|
||||
private const string RepoStatus = "REPO";
|
||||
private const string RecaStatus = "RECA";
|
||||
private const string RehoStatus = "REHO";
|
||||
private const string RedeemedStatus = "REDEEMED";
|
||||
private readonly IEventAlertService _eventAlertService;
|
||||
|
||||
private readonly IEnumerable<IAlertChecker> _accountAlertPreStatusCheckers;
|
||||
private readonly IEnumerable<IAlertChecker> _accountAlertPrimaryCheckers;
|
||||
private readonly IEnumerable<IAlertChecker> _accountAlertPrimaryIgnoreWrittenOffCheckers;
|
||||
|
||||
public AccountAlertCheckService( IEventAlertService eventAlertService)
|
||||
{
|
||||
_eventAlertService = eventAlertService;
|
||||
_accountAlertPreStatusCheckers = new List<IAlertChecker>()
|
||||
{
|
||||
new SkipPaidChecker()
|
||||
};
|
||||
_accountAlertPrimaryCheckers = new List<IAlertChecker>()
|
||||
{
|
||||
new RepoNotMovedChecker( 7),
|
||||
new RepoNotSoldChecker( 60, 30),
|
||||
new StatusChangeChecker( RecaStatus),
|
||||
new StatusChangeChecker( RehoStatus),
|
||||
new TimeInStatusChecker( RehoStatus, 30, 30),
|
||||
new StatusChangeChecker( O4RStatus, new List<string>() { NoStatus, RedeemedStatus, RehoStatus, RecaStatus}),
|
||||
new O4rNoRepoChecker(RepoType.Voluntary),
|
||||
new O4rNoRepoChecker(RepoType.Involuntary),
|
||||
new O4rNoRepoChecker(RepoType.PreSkip),
|
||||
new SkipEndChecker( 91),
|
||||
new SkipNotPaidChecker( 106, 15),
|
||||
new DocumentNotFoundChecker("Notice of Plan to Sell", "LTR35", "701", RepoStatus),
|
||||
new DocumentNotFoundChecker("Notice of Repossession", "LTR34", "701", RepoStatus),
|
||||
new RtcLetterNotFoundChecker("RTC Letter", new List<string> { "RTC 1", "RTC 3" }),
|
||||
new SoldNoProceedsChecker(),
|
||||
new AuctionOnHoldChecker(),
|
||||
new AuctionVinDifferentChecker(),
|
||||
new RedemptionEndedNoTitleOrderedChecker()
|
||||
};
|
||||
_accountAlertPrimaryIgnoreWrittenOffCheckers = new List<IAlertChecker>()
|
||||
{
|
||||
new DebitAdjustmentChecker(),
|
||||
new PaymentReceivedChecker(),
|
||||
new CreditAdjustmentChecker(),
|
||||
new SaleFundsReceivedChecker()
|
||||
};
|
||||
}
|
||||
|
||||
public Task AlertChecksPreStatusReject(RepoAccountProcessingSlip slip)
|
||||
{
|
||||
return CheckForAlerts(_accountAlertPreStatusCheckers, slip);
|
||||
}
|
||||
|
||||
public async Task<EventAlert> ManualAlert(EventAlert alert)
|
||||
{
|
||||
return await _eventAlertService.SaveAsync(alert);
|
||||
}
|
||||
|
||||
public async Task MaybeAlertOnInvalidStatus(RepoAccountProcessingSlip slip)
|
||||
{
|
||||
var allowedStatuses = slip.CurrentSettings?.AllowedStatusChanges.Select(x => x.CurrentStatus);
|
||||
if (slip.UpdatedRepoAccount != null && allowedStatuses != null &&
|
||||
!allowedStatuses.Contains(slip.UpdatedRepoAccount.Status))
|
||||
{
|
||||
await _eventAlertService.SaveAsync(new EventAlert()
|
||||
{
|
||||
RepoId = slip.UpdatedRepoAccount.RepoId,
|
||||
AccountNo = slip.UpdatedRepoAccount.AccountNo,
|
||||
CreatedBy = "Importer",
|
||||
DateAdded = DateTime.Now,
|
||||
Message = $"Collections System Status {slip.UpdatedRepoAccount.Status} Not Valid"
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public async Task AlertChecksPrimary( RepoAccountProcessingSlip slip)
|
||||
{
|
||||
if (slip.TransactionHistory != null && slip.TransactionHistory.Any())
|
||||
{
|
||||
await CheckForAlerts(_accountAlertPrimaryIgnoreWrittenOffCheckers, slip);
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error($"No Transactions Available, skipping Transaction Alerts for {slip.AccountNo}");
|
||||
}
|
||||
|
||||
await CheckForAlerts(_accountAlertPrimaryCheckers, slip);
|
||||
}
|
||||
|
||||
private async Task CheckForAlerts(IEnumerable<IAlertChecker> alertCheckers, RepoAccountProcessingSlip slip)
|
||||
{
|
||||
var eventAlerts = new List<EventAlert>();
|
||||
foreach (var alertChecker in alertCheckers)
|
||||
{
|
||||
try
|
||||
{
|
||||
var createdAlerts = alertChecker.CheckForAlert(slip);
|
||||
if (createdAlerts != null)
|
||||
{
|
||||
eventAlerts.AddRange(createdAlerts);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var accountNumber = slip.AccountNo ?? "Account Number Not Found";
|
||||
Log.Error(ex, $"Error Checking Alert {alertChecker.GetType()} For Account: {accountNumber}");
|
||||
}
|
||||
}
|
||||
|
||||
await _eventAlertService.SaveRangeAsync(eventAlerts);
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Domain.Processing;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.Producer
|
||||
{
|
||||
public interface IAccountAlertCheckService
|
||||
{
|
||||
/**
|
||||
* These alerts run before the account is checked to see if it was moved to an un-allowed status.
|
||||
* What is this moves the status to an un-allowed status?
|
||||
*/
|
||||
Task AlertChecksPreStatusReject(RepoAccountProcessingSlip slip);
|
||||
|
||||
/**
|
||||
* These alerts run after the account has been updated or created and is the primary set of alert checks.
|
||||
*/
|
||||
Task AlertChecksPrimary(RepoAccountProcessingSlip slip);
|
||||
|
||||
Task<EventAlert> ManualAlert(EventAlert alert);
|
||||
Task MaybeAlertOnInvalidStatus(RepoAccountProcessingSlip processingSlip);
|
||||
}
|
||||
}
|
||||
+11
@@ -0,0 +1,11 @@
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.Domain.Processing;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Producer;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.Producer
|
||||
{
|
||||
public interface IRepoAccountProcessor
|
||||
{
|
||||
Task<RepoProcessingResult> HandleAccount(ProcessingItem item);
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
using System.Collections.Generic;
|
||||
using LCU.API.Collections.CollectionsApiLib.Models;
|
||||
using LCU.API.Collections.RepoApi.Domain.Processing;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Consumer;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Settings;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.Producer
|
||||
{
|
||||
public interface IRepoAccountProcessorService
|
||||
{
|
||||
RepoAccount CreateRepoAccount(TemenosAccount account, TemenosCase recoveryCase, LoanDetail detail);
|
||||
void CheckForRejectedStatusChange(RepoAccount originalRepoAccount, RepoAccount updatedRepoAccount, Settings settings);
|
||||
RepoAccount UpdateRepoAccountFromParts(RepoAccountProcessingSlip slip);
|
||||
void UpdateRepoAccountDateClosed(RepoAccount repoAccount);
|
||||
void TryUpdateCollateralRetitleDate(RepoAccount repo, TemenosCase temenosCase);
|
||||
void AddAccountUpdateRecord(RepoAccountProcessingSlip slip);
|
||||
RepoCompanySelection CreateNewRepoCompanyAssignment(Collateral collateral, TemenosCase temenosCase, int? newRepoCompanyId);
|
||||
void TryUpdateOrCreateRepoCompanyAssignment(RepoAccount repoAccount,
|
||||
TemenosCase temenosCase, Settings settings);
|
||||
int? MapRepoAgentIdToRepoCompanyId(string repoAgentId, IReadOnlyCollection<RepoCompany> repoCompanies);
|
||||
}
|
||||
}
|
||||
+331
@@ -0,0 +1,331 @@
|
||||
using System;
|
||||
using System.Text.RegularExpressions;
|
||||
using System.Threading.Tasks;
|
||||
using LCU.API.Collections.RepoApi.DataAccess;
|
||||
using LCU.API.Collections.RepoApi.Domain.Extensions;
|
||||
using LCU.API.Collections.RepoApi.Domain.Processing;
|
||||
using LCU.API.Collections.RepoApi.Exceptions;
|
||||
using LCU.API.Collections.RepoApi.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Producer;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.Producer
|
||||
{
|
||||
public class RepoAccountProcessor : IRepoAccountProcessor
|
||||
{
|
||||
private readonly IRepoAccountProcessorService _repoAccountProcessorService;
|
||||
private readonly IAccountAlertCheckService _accountAlertCheckService;
|
||||
private readonly IEmailer _emailer;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ICollectionsDao _collectionsDao;
|
||||
private readonly ICoreDao _coreDao;
|
||||
private readonly IRepoAccountService _repoAccountService;
|
||||
private readonly IRepoLetterService _repoLetterService;
|
||||
private readonly IAuctionAssignmentService _auctionAssignmentService;
|
||||
public const string UserStatusFieldOld = "User Status Code 1";
|
||||
public const int CaseTypeRepo = 7; // Source: collections API configuration call
|
||||
private const string O4RStatus = "O4R";
|
||||
private const string NoStatus = "";
|
||||
private const string RepoStatus = "REPO";
|
||||
private const string RecaStatus = "RECA";
|
||||
private const string RehoStatus = "REHO";
|
||||
private const string RedeemedStatus = "REDEEMED";
|
||||
private const string TemenosQueuedBy = "Temenos";
|
||||
|
||||
public RepoAccountProcessor(
|
||||
IRepoAccountProcessorService repoAccountProcessorService,
|
||||
IAccountAlertCheckService accountAlertCheckService,
|
||||
ICollectionsDao collectionsDao,
|
||||
IEmailer emailer,
|
||||
ISettingsService settingsService,
|
||||
IRepoAccountService repoAccountService,
|
||||
IRepoLetterService repoLetterService, ICoreDao coreDao,
|
||||
IAuctionAssignmentService auctionAssignmentService)
|
||||
{
|
||||
_repoAccountProcessorService = repoAccountProcessorService;
|
||||
_accountAlertCheckService = accountAlertCheckService;
|
||||
_collectionsDao = collectionsDao;
|
||||
_emailer = emailer;
|
||||
_settingsService = settingsService;
|
||||
_repoAccountService = repoAccountService;
|
||||
_repoLetterService = repoLetterService;
|
||||
_coreDao = coreDao;
|
||||
_auctionAssignmentService = auctionAssignmentService;
|
||||
}
|
||||
|
||||
public async Task<RepoProcessingResult> HandleAccount(ProcessingItem item)
|
||||
{
|
||||
var processingSlip = await CreateProcessingSlip(item);
|
||||
if (IsAutoCloseAccount(processingSlip))
|
||||
{
|
||||
Log.Debug($"Auto-Closing account {processingSlip.AccountNo} due to no Temenos Account.");
|
||||
await AutoCloseAccount(processingSlip);
|
||||
return RepoProcessingResult.Autoclosed;
|
||||
}
|
||||
|
||||
processingSlip.AssertReadyForPreChecks();
|
||||
|
||||
// 9640 Attempt to pre-check for a matching status when case is missing.
|
||||
// If no found case, and the status hasn't changed; instead of failing just skip the unconverted account.
|
||||
if (IsUnconvertedIntellectAccount(processingSlip))
|
||||
{
|
||||
Log.Debug($"Repo Processor: {item.AccountNo} is not updated for Temenos; skipping.");
|
||||
return RepoProcessingResult.Unconverted;
|
||||
}
|
||||
|
||||
processingSlip.RecoveryCase = processingSlip.RecoveryAccount.FindRecoveryCaseOrDefault(processingSlip.CaseId);
|
||||
|
||||
if (!IsPossiblyRepossessionCase(processingSlip))
|
||||
{
|
||||
return RepoProcessingResult.Ineligible;
|
||||
}
|
||||
|
||||
processingSlip.AssertReadyForFullProcessing();
|
||||
|
||||
if (ShouldCreateNewAccount(processingSlip))
|
||||
{
|
||||
Log.Debug($"Creating new repo account for {item.AccountNo} : {item.CaseId} ");
|
||||
processingSlip.UpdatedRepoAccount = await CreateNewRepoAccount(processingSlip);
|
||||
await _accountAlertCheckService.MaybeAlertOnInvalidStatus(processingSlip);
|
||||
processingSlip = await AddAlertDataToProcessingSlip(processingSlip);
|
||||
processingSlip.AssertReadyForAlertChecks();
|
||||
await _accountAlertCheckService.AlertChecksPrimary(processingSlip);
|
||||
return RepoProcessingResult.Created;
|
||||
}
|
||||
|
||||
if (!processingSlip.ExistingRepoAccount.HasAnyCollateral())
|
||||
{
|
||||
await _emailer.MaybeSendO4rNoCollateralEmail(processingSlip.ExistingRepoAccount);
|
||||
throw new MissingRequirementException("Existing Account Collateral",
|
||||
$"{processingSlip.AccountNo} did not have collateral on existing account.");
|
||||
}
|
||||
|
||||
Log.Debug($"Updating Repo account for {item.AccountNo} : {item.CaseId}");
|
||||
processingSlip.UpdatedRepoAccount = await UpdateExistingRepoAccount(processingSlip);
|
||||
|
||||
//Add check for recent account repo closing when in RECA status to prevent reprocessing and new repo account creation with a DateClosed date = null
|
||||
//The DateClosed date = null will ensure the account gets reprocessed, we don't want alert checks with these accounts
|
||||
|
||||
if (processingSlip.ExistingRepoAccount.DateClosed == null && processingSlip.UpdatedRepoAccount.DateClosed != null)
|
||||
{
|
||||
return RepoProcessingResult.Updated;
|
||||
}
|
||||
|
||||
processingSlip = await AddAlertDataToProcessingSlip(processingSlip);
|
||||
processingSlip.AssertReadyForAlertChecks();
|
||||
await _accountAlertCheckService.AlertChecksPrimary(processingSlip);
|
||||
return RepoProcessingResult.Updated;
|
||||
}
|
||||
|
||||
// New method to give a chance to filter out non-repo cases and mark ineligible
|
||||
private bool IsPossiblyRepossessionCase(RepoAccountProcessingSlip slip)
|
||||
{
|
||||
if (slip.RecoveryCase == null) return true;
|
||||
try
|
||||
{
|
||||
if (slip.RecoveryCase.CaseTypeId != CaseTypeRepo)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
StatusService.ConvertStatus(slip.RecoveryCase.RepoStatus);
|
||||
}
|
||||
catch (IneligibleAccountException)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task AutoCloseAccount(RepoAccountProcessingSlip processingSlip)
|
||||
{
|
||||
AppUser appUser = new AppUser()
|
||||
{
|
||||
Email = processingSlip.RequestedBy.QueuedBy,
|
||||
FirstName = "System",
|
||||
LastName = "System"
|
||||
};
|
||||
processingSlip.ExistingRepoAccount.DateClosed = DateTime.Now;
|
||||
processingSlip.ExistingRepoAccount.AccountLastProcessed = DateTime.Now;
|
||||
await _repoAccountService.UpdateAsync(processingSlip.ExistingRepoAccount, appUser);
|
||||
}
|
||||
|
||||
private bool IsAutoCloseAccount(RepoAccountProcessingSlip processingSlip)
|
||||
{
|
||||
// If recovery account couldn't be fetched, that would have been an exception.
|
||||
// If recovery account is null, it means "not there", so close the RepoAccount.
|
||||
// This means close the RepoAccount because the account
|
||||
// was likely not brought over from Intellect. If something DOES happen, just create
|
||||
// a new account later....
|
||||
return (processingSlip.RecoveryAccount == null
|
||||
&& processingSlip.ExistingRepoAccount != null
|
||||
&& processingSlip.ExistingRepoAccount.DateClosed == null);
|
||||
}
|
||||
|
||||
public async Task<RepoAccountProcessingSlip> CreateProcessingSlip(ProcessingItem item)
|
||||
{
|
||||
var accountNo = VerifyAccountNo(item);
|
||||
|
||||
var slip = new RepoAccountProcessingSlip
|
||||
{
|
||||
AccountNo = accountNo,
|
||||
CurrentSettings = await _settingsService.GetCachedRepoSettingsAsync(),
|
||||
RecoveryAccount = await _collectionsDao.RetrieveAccountAsync(accountNo),
|
||||
ExistingRepoAccount = await _repoAccountService.GetNewestOpenAccount(accountNo),
|
||||
Loan = await _coreDao.RetrieveLoanAsync(accountNo),
|
||||
CaseId = VerifyCaseId(item.CaseId),
|
||||
RequestedBy = item
|
||||
};
|
||||
|
||||
if (slip.RecoveryAccount != null)
|
||||
{
|
||||
slip.Letters = await _repoLetterService.ListByAccountIdAsync(slip.RecoveryAccount.AccountId);
|
||||
}
|
||||
|
||||
return slip;
|
||||
}
|
||||
|
||||
public async Task<RepoAccountProcessingSlip> AddAlertDataToProcessingSlip(RepoAccountProcessingSlip slip)
|
||||
{
|
||||
slip.TransactionHistory = await _coreDao.RetrieveTransactionsAsync(slip.AccountNo);
|
||||
slip.Member = await _coreDao.RetrieveMemberAsync(slip.AccountNo);
|
||||
var uniqueIds = slip.UpdatedRepoAccount.GetUniqueAuctionAssignmentIds();
|
||||
slip.AuctionAssignments = await _auctionAssignmentService.GetListByIdsAsync(uniqueIds);
|
||||
return slip;
|
||||
}
|
||||
|
||||
public bool IsUnconvertedIntellectAccount(RepoAccountProcessingSlip repoAccountSlip)
|
||||
{
|
||||
var recoveryAccount = repoAccountSlip.RecoveryAccount;
|
||||
var caseId = repoAccountSlip.CaseId;
|
||||
var originalRepoAccount = repoAccountSlip.ExistingRepoAccount;
|
||||
if (!recoveryAccount.HasRecoveryCase(caseId) && recoveryAccount.HasOldIntellectStatus())
|
||||
{
|
||||
Log.Information(
|
||||
$"Repo Processor: {repoAccountSlip.AccountNo} has no case, but user status code. Running pre-check.");
|
||||
return (originalRepoAccount.Status == recoveryAccount.CustomFieldMap[UserStatusFieldOld]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public int VerifyCaseId(string itemCaseId)
|
||||
{
|
||||
try
|
||||
{
|
||||
return string.IsNullOrWhiteSpace(itemCaseId) ? 0 : Int32.Parse(itemCaseId);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
throw new IneligibleAccountException(
|
||||
$"Bad CaseId {itemCaseId}",
|
||||
$"The CaseId is present, but in a non-integer format.");
|
||||
}
|
||||
}
|
||||
|
||||
public string VerifyAccountNo(ProcessingItem item)
|
||||
{
|
||||
if (item == null)
|
||||
{
|
||||
throw new IneligibleAccountException("Processing Item", "The processing item was null.");
|
||||
}
|
||||
|
||||
if (item.AccountNo == null)
|
||||
{
|
||||
throw new IneligibleAccountException(
|
||||
"Null AccountNo",
|
||||
"There should never be null accounts on the queue.");
|
||||
}
|
||||
|
||||
// if (Regex.IsMatch(item.AccountNo, "^[a-zA-Z0-9]*$"))
|
||||
// {
|
||||
// throw new IneligibleAccountException(
|
||||
// "AccountNo",
|
||||
// "AccountNo contains alphabetical or symbol characters");
|
||||
// }
|
||||
|
||||
return item.AccountNo;
|
||||
}
|
||||
|
||||
public async Task<RepoAccount> UpdateExistingRepoAccount(RepoAccountProcessingSlip slip)
|
||||
{
|
||||
AppUser appUser = new AppUser()
|
||||
{
|
||||
Email = slip.RequestedBy.QueuedBy,
|
||||
FirstName = "System",
|
||||
LastName = "System"
|
||||
};
|
||||
|
||||
if (ShouldRunCompleteUpdate(slip))
|
||||
{
|
||||
RepoAccount updatedRepoAccount =
|
||||
_repoAccountProcessorService.UpdateRepoAccountFromParts(slip);
|
||||
await _accountAlertCheckService.AlertChecksPreStatusReject(slip);
|
||||
|
||||
_repoAccountProcessorService.CheckForRejectedStatusChange(slip.ExistingRepoAccount, updatedRepoAccount,
|
||||
slip.CurrentSettings);
|
||||
_repoAccountProcessorService.TryUpdateOrCreateRepoCompanyAssignment(updatedRepoAccount,
|
||||
slip.RecoveryCase, slip.CurrentSettings);
|
||||
_repoAccountProcessorService.TryUpdateCollateralRetitleDate(updatedRepoAccount, slip.RecoveryCase);
|
||||
await _repoAccountService.UpdateAsync(updatedRepoAccount, appUser);
|
||||
|
||||
return updatedRepoAccount;
|
||||
}
|
||||
|
||||
_repoAccountProcessorService.TryUpdateOrCreateRepoCompanyAssignment(slip.ExistingRepoAccount,
|
||||
slip.RecoveryCase, slip.CurrentSettings);
|
||||
_repoAccountProcessorService.AddAccountUpdateRecord(slip);
|
||||
_repoAccountProcessorService.TryUpdateCollateralRetitleDate(slip.ExistingRepoAccount, slip.RecoveryCase);
|
||||
_repoAccountProcessorService.UpdateRepoAccountDateClosed(slip.ExistingRepoAccount);
|
||||
await _repoAccountService.UpdateAsync(slip.ExistingRepoAccount, appUser);
|
||||
return slip.ExistingRepoAccount;
|
||||
}
|
||||
|
||||
public bool ShouldRunCompleteUpdate(RepoAccountProcessingSlip slip)
|
||||
{
|
||||
if (slip.ExistingRepoAccount == null)
|
||||
{
|
||||
throw new MissingRequirementException("Existing Repo Account",
|
||||
$"Existing Repo Account required for update check, {slip.AccountNo}");
|
||||
}
|
||||
|
||||
var newStatus = StatusService.ConvertStatus(slip.RecoveryCase.RepoStatus);
|
||||
|
||||
var dateLastPaid = slip.RecoveryAccount.DateLastPaid != null
|
||||
? DateTime.Parse(slip.RecoveryAccount.DateLastPaid)
|
||||
: slip.ExistingRepoAccount.LastPaymentDate;
|
||||
var refStatusChanged = slip.RecoveryAccount.AccountStatus != slip.ExistingRepoAccount.ReferenceStatus;
|
||||
|
||||
// This is a special case, it picked up the written off flag, before there was a written off date.
|
||||
if (string.Equals(slip.ExistingRepoAccount.ReferenceStatus, RepoAccountProcessorService.WrittenOff,
|
||||
StringComparison.CurrentCultureIgnoreCase)
|
||||
&& slip.ExistingRepoAccount.WrittenOffDate == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var needsRedemptionExpired = slip.ExistingRepoAccount.RedemptionExpiredDate == null
|
||||
&& (slip.RecoveryCase.RedemptionExpiredDateIl != null ||
|
||||
slip.RecoveryCase.RedemptionExpiredDateWi != null);
|
||||
|
||||
return !string.Equals(newStatus, slip.ExistingRepoAccount.Status, StringComparison.CurrentCultureIgnoreCase)
|
||||
|| slip.ExistingRepoAccount.LastPaymentDate != dateLastPaid ||
|
||||
slip.ExistingRepoAccount.Balance != slip.RecoveryAccount.Balance
|
||||
|| needsRedemptionExpired || refStatusChanged;
|
||||
}
|
||||
|
||||
public async Task<RepoAccount> CreateNewRepoAccount(RepoAccountProcessingSlip slip)
|
||||
{
|
||||
var repoAccount = _repoAccountProcessorService.CreateRepoAccount(slip.RecoveryAccount, slip.RecoveryCase, slip.Loan);
|
||||
var newRepoAccount = await _repoAccountService.SaveAsync(repoAccount);
|
||||
return newRepoAccount;
|
||||
}
|
||||
|
||||
public bool ShouldCreateNewAccount(RepoAccountProcessingSlip slip)
|
||||
{
|
||||
return slip != null && (slip.ExistingRepoAccount == null || slip.ExistingRepoAccount.DateClosed != null);
|
||||
}
|
||||
}
|
||||
}
|
||||
+585
@@ -0,0 +1,585 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using LCU.API.Collections.CollectionsApiLib.Models;
|
||||
using LCU.API.Collections.RepoApi.DataAccess;
|
||||
using LCU.API.Collections.RepoApi.Domain.Processing;
|
||||
using LCU.API.Collections.RepoApi.Exceptions;
|
||||
using LCU.API.Collections.RepoApi.Models.Results;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Consumer;
|
||||
using LCU.Shared.Collections.RepoApiLib.Models.Settings;
|
||||
using Serilog;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Services.Producer
|
||||
{
|
||||
public class RepoAccountProcessorService : IRepoAccountProcessorService
|
||||
{
|
||||
private const string RedeemedStatus = "REDEEMED";
|
||||
private const string PaidSkipStatus = "PSKP";
|
||||
private const string RepoCancelledStatus = "RECA";
|
||||
public const string WrittenOff = "Written Off";
|
||||
public const string SixtyPLusUserAssignedLabel = "SixtyPlus User Assigned";
|
||||
private readonly ICoreDao _coreDao;
|
||||
private readonly IRedemptionExpiredDateCalculator _redemptionExpiredDateCalculator;
|
||||
private readonly IAccountAlertCheckService _accountAlertCheckService;
|
||||
private readonly IEventAlertService _eventAlertService;
|
||||
private readonly IRepoCompanySelectionService _repoCompanySelectionService;
|
||||
|
||||
public RepoAccountProcessorService(ICoreDao coreDao,
|
||||
IRedemptionExpiredDateCalculator redemptionExpiredDateCalculator,
|
||||
IAccountAlertCheckService accountAlertCheckService,
|
||||
IEventAlertService eventAlertService, IRepoCompanySelectionService repoCompanySelectionService)
|
||||
{
|
||||
_coreDao = coreDao;
|
||||
_redemptionExpiredDateCalculator = redemptionExpiredDateCalculator;
|
||||
_accountAlertCheckService = accountAlertCheckService;
|
||||
_eventAlertService = eventAlertService;
|
||||
_repoCompanySelectionService = repoCompanySelectionService;
|
||||
}
|
||||
|
||||
// This process determined from the original batch code. (LCU.CollectionRepossessionTracking::IntellecRepoImport)
|
||||
// -> A) Get XP/Core/Apex Account
|
||||
// -> B) Get XP/Core/Apex Loan
|
||||
// -> B.1) Fields needed: Branch, Next due date
|
||||
// ---> If it can't find a loan, it does nothing... what do we WANT do?
|
||||
// -> C) Get XP/Core/Apex Loan Collateral
|
||||
// ---> If there is no collateral, it does nothing... what do we WANT do?
|
||||
// -> D) Get XP/Core/Apex Borrowers (Individuals)
|
||||
// ---> No borrowers throws an exception? ... what do we WANT do?
|
||||
// -> E) Create Repo Account from Loan, Collateral, Borrowers and Temenos Account
|
||||
public RepoAccount CreateRepoAccount(TemenosAccount account, TemenosCase recoveryCase, LoanDetail loanDetail)
|
||||
{
|
||||
var collateralDetailRequest = _coreDao.RetrieveCollateralAsync(account.AccountIdentifier);
|
||||
|
||||
var collateralDetail = collateralDetailRequest.GetAwaiter().GetResult();
|
||||
if (collateralDetail == null || !collateralDetail.Any())
|
||||
{
|
||||
throw new AccountCreationFailed(
|
||||
$"Repo-Account Not Created using Account with AccountNo: {account.AccountIdentifier}. No Collateral.");
|
||||
}
|
||||
|
||||
var borrowers = ConstructRepoAccountBorrowers(loanDetail);
|
||||
if (borrowers == null || !borrowers.Any())
|
||||
{
|
||||
throw new AccountCreationFailed(
|
||||
$"Repo-Account Not Created using Account with AccountNo: {account.AccountIdentifier}. No Borrowers.");
|
||||
}
|
||||
|
||||
var repoAccount = ConstructRepoAccountFromParts(loanDetail, account, recoveryCase);
|
||||
repoAccount.Borrowers = borrowers;
|
||||
repoAccount.Collaterals = ConstructCollateralFromDetails(collateralDetail);
|
||||
TryUpdateCollateralRetitleDate(repoAccount, recoveryCase);
|
||||
|
||||
Log.Information($"Created Repo-Account for Not-Found Account. AccountNo: {account.AccountIdentifier}");
|
||||
return repoAccount;
|
||||
}
|
||||
|
||||
public List<Borrower> ConstructRepoAccountBorrowers(LoanDetail coreAccount)
|
||||
{
|
||||
List<Borrower> borrowers = new List<Borrower>();
|
||||
try
|
||||
{
|
||||
foreach (var coreAccountBorrower in coreAccount.Borrowers)
|
||||
{
|
||||
var borrower = new Borrower()
|
||||
{
|
||||
IndividualName = coreAccountBorrower.Name,
|
||||
IndividualId = coreAccountBorrower.IndividualId.ToString(),
|
||||
OwnershipType = (int)ConvertBorrowerType(coreAccountBorrower.Type)
|
||||
};
|
||||
borrowers.Add(borrower);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, "Error in ConstructRepoAccountBorrowers");
|
||||
}
|
||||
|
||||
return borrowers;
|
||||
}
|
||||
|
||||
public List<Collateral> ConstructCollateralFromDetails(IReadOnlyCollection<CollateralDetail> collateralDetail)
|
||||
{
|
||||
List<Collateral> collateralList = new List<Collateral>();
|
||||
foreach (var detail in collateralDetail)
|
||||
{
|
||||
var collateral = new Collateral()
|
||||
{
|
||||
BodyStyle = null,
|
||||
CollateralTypeId = null,
|
||||
Description = detail.Description,
|
||||
Inoperable = null,
|
||||
RefCollateralId = detail.CollateralId.ToString()
|
||||
};
|
||||
if (detail.Other != null)
|
||||
{
|
||||
collateral.Vin = detail.Other.MiscellaneousInformation;
|
||||
}
|
||||
|
||||
if (detail.Vehicle != null)
|
||||
{
|
||||
collateral.Make = detail.Vehicle.Make;
|
||||
collateral.Model = detail.Vehicle.Model;
|
||||
collateral.Vin = detail.Vehicle.VIN;
|
||||
collateral.Year = detail.Vehicle.Year;
|
||||
collateral.BodyStyle = detail.Vehicle.BodyStyle;
|
||||
}
|
||||
|
||||
collateralList.Add(collateral);
|
||||
}
|
||||
|
||||
return collateralList;
|
||||
}
|
||||
|
||||
public RepoAccount ConstructRepoAccountFromParts(LoanDetail coreAccount, TemenosAccount account,
|
||||
TemenosCase recoveryCase)
|
||||
{
|
||||
DateTime? lastPaymentDate = null;
|
||||
DateTime? dateFirstDelinquent = null;
|
||||
|
||||
var lastPaymentResult = GetDateTimeFromString(account.DateLastPaid);
|
||||
if (lastPaymentResult.IsSuccess)
|
||||
{
|
||||
lastPaymentDate = lastPaymentResult.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning(
|
||||
$"Construct Repo Account from temenos accountNo: {account.AccountNumber}. Could not parse last payment result : {account.DateLastPaid}.",
|
||||
lastPaymentResult, account);
|
||||
}
|
||||
|
||||
|
||||
var dateFirstDelinquentResult = GetDateTimeFromString(account.DateFirstDelinquent);
|
||||
if (dateFirstDelinquentResult.IsSuccess)
|
||||
{
|
||||
dateFirstDelinquent = dateFirstDelinquentResult.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning(
|
||||
$"Construct Repo Account from temenos accountNo: {account.AccountNumber}. failed parsing dateFirstDelinquent {account.DateFirstDelinquent}",
|
||||
dateFirstDelinquentResult, account);
|
||||
// This is no longer a "MissingRequirementException", per Bill Johns.
|
||||
}
|
||||
|
||||
var sixtyPlusUserAssignedNumber = GetSixtyPlusUserNumber(account);
|
||||
|
||||
var repoAccount = new RepoAccount()
|
||||
{
|
||||
DueToPayDate = coreAccount.NextDueDate,
|
||||
Balance = account.Balance,
|
||||
AccountTypeCode = account.AccountType.Code,
|
||||
OrigAccountTypeCode = account.AccountType.Code,
|
||||
LoanNo = long.Parse(account.AccountIdentifier),
|
||||
MemberNo = MemberIdFromAccountId(account.AccountIdentifier),
|
||||
Branch = account.Branch != null ? int.Parse(account.Branch) : 0,
|
||||
AccountNo = account.AccountIdentifier,
|
||||
Status = StatusService.ConvertStatus(recoveryCase.RepoStatus),
|
||||
O4RDate = recoveryCase.CreatedDate,
|
||||
Collector = account.LoanOfficer,
|
||||
DateFirstDelinquent = dateFirstDelinquent,
|
||||
DelinquentAmount = account.DQAmount,
|
||||
CurrentDelinquentAmount = account.DQAmount,
|
||||
SubStatus = "",
|
||||
DaysDelinquent = account.DaysDq,
|
||||
LastPaymentDate = lastPaymentDate,
|
||||
LstPmtAmt = account.AmountLastPaid,
|
||||
OrigAmount = account.OriginalLoanAmount,
|
||||
RequiredPaymentAmount = account.DQAmount,
|
||||
PrimaryIndividualId = coreAccount.IndividualId[0].ToString(),
|
||||
Bankruptcy = $"{account.BankruptcyFlag}",
|
||||
DateFirstIntellect = recoveryCase.CreatedDate,
|
||||
ReferenceId = account.AccountId,
|
||||
ReferenceCaseId = recoveryCase.CaseId,
|
||||
AccountLastProcessed = DateTime.Now,
|
||||
ReferenceStatus = account.AccountStatus,
|
||||
WrittenOffDate = coreAccount.Status == WrittenOff ? coreAccount.WrittenOffDate : (DateTime?)null,
|
||||
SixtyPlusCollectorId = sixtyPlusUserAssignedNumber
|
||||
};
|
||||
|
||||
return repoAccount;
|
||||
}
|
||||
|
||||
public long MemberIdFromAccountId(string accountId)
|
||||
{
|
||||
return _coreDao.RetrieveMemberAsync(accountId).GetAwaiter().GetResult().MemberId;
|
||||
}
|
||||
|
||||
public RepoAccount UpdateRepoAccountFromParts(RepoAccountProcessingSlip slip)
|
||||
{
|
||||
RepoAccount originalRepoAccount = slip.ExistingRepoAccount;
|
||||
TemenosAccount account = slip.RecoveryAccount;
|
||||
TemenosCase recoveryCase = slip.RecoveryCase;
|
||||
IReadOnlyCollection<RepoLetter> letters = slip.Letters;
|
||||
var redemptionExpiredDate = _redemptionExpiredDateCalculator.CalculateAsync(recoveryCase, letters);
|
||||
|
||||
DateTime? lastPaymentDate = null;
|
||||
DateTime? dateFirstDelinquent = originalRepoAccount.DateFirstDelinquent;
|
||||
|
||||
var lastPaymentResult = GetDateTimeFromString(account.DateLastPaid);
|
||||
if (lastPaymentResult.IsSuccess)
|
||||
{
|
||||
lastPaymentDate = lastPaymentResult.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Warning(
|
||||
$"Repo account {originalRepoAccount.RepoId} ({originalRepoAccount.AccountNo}). Could not parse last payment result : {account.DateLastPaid}.",
|
||||
lastPaymentResult);
|
||||
}
|
||||
|
||||
var dateFirstDelinquentResult = GetDateTimeFromString(account.DateFirstDelinquent);
|
||||
if (dateFirstDelinquentResult.IsSuccess)
|
||||
{
|
||||
dateFirstDelinquent = dateFirstDelinquentResult.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
Log.Error(
|
||||
$"Repo account {originalRepoAccount.RepoId} ({originalRepoAccount.AccountNo}) failed parsing dateFirstDelinquent {account.DateFirstDelinquent}",
|
||||
dateFirstDelinquentResult);
|
||||
// This is no longer a "MissingRequirementException", per Bill Johns.
|
||||
}
|
||||
|
||||
var sixtyPlusUserAssignedNumber = GetSixtyPlusUserNumber(account);
|
||||
|
||||
var repoAccount = new RepoAccount()
|
||||
{
|
||||
RepoId = originalRepoAccount.RepoId,
|
||||
DueToPayDate = account.DueDate,
|
||||
Balance = account.Balance,
|
||||
AccountTypeCode = account.AccountType.Code,
|
||||
OrigAccountTypeCode = originalRepoAccount.OrigAccountTypeCode,
|
||||
LoanNo = originalRepoAccount.LoanNo,
|
||||
MemberNo = originalRepoAccount.MemberNo,
|
||||
Branch = originalRepoAccount.Branch,
|
||||
AccountNo = originalRepoAccount.AccountNo,
|
||||
Status = StatusService.ConvertStatus(recoveryCase.RepoStatus),
|
||||
O4RDate = originalRepoAccount.O4RDate,
|
||||
Collector = account.LoanOfficer,
|
||||
DateFirstDelinquent = dateFirstDelinquent,
|
||||
DelinquentAmount = account.DQAmount,
|
||||
CurrentDelinquentAmount = account.DQAmount,
|
||||
SubStatus = "",
|
||||
DaysDelinquent = originalRepoAccount.DaysDelinquent, //leave this set to days delinquent when added.
|
||||
LastPaymentDate = lastPaymentDate,
|
||||
LstPmtAmt = account.AmountLastPaid ?? 0,
|
||||
OrigAmount = account.OriginalLoanAmount ?? 0,
|
||||
RedemptionExpiredDate = redemptionExpiredDate,
|
||||
RequiredPaymentAmount = account.DQAmount,
|
||||
PrimaryIndividualId = originalRepoAccount.PrimaryIndividualId,
|
||||
Bankruptcy = $"{account.BankruptcyFlag}",
|
||||
Collaterals = originalRepoAccount.Collaterals,
|
||||
RepoStatusChanges = originalRepoAccount.RepoStatusChanges,
|
||||
ManualRedemptionDate = originalRepoAccount.ManualRedemptionDate,
|
||||
DateFirstIntellect = originalRepoAccount.DateFirstIntellect,
|
||||
Borrowers = originalRepoAccount.Borrowers,
|
||||
PaidSkipAmount = originalRepoAccount.PaidSkipAmount,
|
||||
RefundStatus = originalRepoAccount.RefundStatus,
|
||||
ReferenceId = account.AccountId,
|
||||
ReferenceCaseId = recoveryCase.CaseId,
|
||||
AccountLastProcessed = DateTime.Now,
|
||||
ReferenceStatus = account.AccountStatus,
|
||||
WrittenOffDate = slip.Loan.Status == WrittenOff ? slip.Loan.WrittenOffDate : (DateTime?)null,
|
||||
SixtyPlusCollectorId = sixtyPlusUserAssignedNumber
|
||||
};
|
||||
|
||||
return repoAccount;
|
||||
}
|
||||
|
||||
/**
|
||||
* High Risk function because of string comparison and unknown mapping.
|
||||
* The old system got the integer from the database.
|
||||
* The new system gets a string from the web service.
|
||||
* This maps the new string to the old integer, if borrower types start
|
||||
* showing up wrong, look here...
|
||||
*/
|
||||
public OwnershipType ConvertBorrowerType(string type)
|
||||
{
|
||||
if (type != null && string.Equals(type.Trim(), "Borrower", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return OwnershipType.Borrower;
|
||||
}
|
||||
|
||||
if (type != null && string.Equals(type.Trim(), "Co-Borrower", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return OwnershipType.Coborrower;
|
||||
}
|
||||
|
||||
return OwnershipType.Other;
|
||||
}
|
||||
|
||||
public void CheckForRejectedStatusChange(RepoAccount originalRepoAccount, RepoAccount updatedRepoAccount,
|
||||
Settings settings)
|
||||
{
|
||||
if (!IsStatusMatch(originalRepoAccount, updatedRepoAccount.Status) &&
|
||||
!IsStatusChangeAllowed(originalRepoAccount.Status, updatedRepoAccount.Status,
|
||||
settings.AllowedStatusChanges))
|
||||
{
|
||||
Log.Warning(
|
||||
$"Status Change Reverted for Account {updatedRepoAccount.AccountNo} from {originalRepoAccount.Status} to {updatedRepoAccount.Status}");
|
||||
|
||||
_accountAlertCheckService.ManualAlert(new EventAlert()
|
||||
{
|
||||
RepoId = updatedRepoAccount.RepoId,
|
||||
AccountNo = updatedRepoAccount.AccountNo,
|
||||
CreatedBy = "Importer",
|
||||
DateAdded = DateTime.Now,
|
||||
Message = "Collections System Status Does Not Match Repo Management",
|
||||
SubMessage =
|
||||
$"Invalid Status Change from {originalRepoAccount.Status} to {updatedRepoAccount.Status}"
|
||||
});
|
||||
updatedRepoAccount.Status = originalRepoAccount.Status;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsStatusChangeAllowed(string oldStatus, string newStatus,
|
||||
IEnumerable<AllowedStatusChange> settingsAllowedStatusChanges)
|
||||
{
|
||||
foreach (var allowedStatusChange in settingsAllowedStatusChanges)
|
||||
{
|
||||
if (IsStringMatch(allowedStatusChange.CurrentStatus, oldStatus) &&
|
||||
IsStringMatch(allowedStatusChange.AllowedChangeStatus, newStatus))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool IsStatusMatch(RepoAccount repo, string status)
|
||||
{
|
||||
if (repo?.Status == null || status == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return string.Equals(repo.Status.Trim(), status.Trim(), StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public bool IsStringMatch(string x, string y)
|
||||
{
|
||||
if (x == null || y == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return string.Equals(x.Trim(), y.Trim(), StringComparison.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public void UpdateRepoAccountDateClosed(RepoAccount repoAccount)
|
||||
{
|
||||
// Close the account after four days in certain statuses
|
||||
if ((repoAccount.Status == PaidSkipStatus || repoAccount.Status == RedeemedStatus || repoAccount.Status == RepoCancelledStatus)
|
||||
&& repoAccount.RepoStatusChanges.LastOrDefault(x => x.StatusTo == repoAccount.Status)?.ChangedDate <
|
||||
DateTime.Now.AddDays(-3).Date)
|
||||
{
|
||||
repoAccount.DateClosed = DateTime.Now;
|
||||
repoAccount.AccountLastProcessed = DateTime.Now;
|
||||
// TODO calling this still has a side effect.
|
||||
_eventAlertService.MarkAllAsRead(repoAccount.AccountNo).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
|
||||
public void TryUpdateCollateralRetitleDate(RepoAccount repo, TemenosCase temenosCase)
|
||||
{
|
||||
DateTime? retitledDate = (temenosCase.TitleOrdered) ? temenosCase.TitleOrderedDate : null;
|
||||
|
||||
foreach (Collateral collateral in repo.Collaterals)
|
||||
{
|
||||
collateral.RetitledInLandmarkName = retitledDate;
|
||||
}
|
||||
}
|
||||
|
||||
// This exists to update accounts not originally created from Temenos
|
||||
public void AddAccountUpdateRecord(RepoAccountProcessingSlip slip)
|
||||
{
|
||||
slip.ExistingRepoAccount.ReferenceId = slip.RecoveryAccount.AccountId;
|
||||
slip.ExistingRepoAccount.ReferenceCaseId = slip.RecoveryCase.CaseId;
|
||||
slip.ExistingRepoAccount.AccountLastProcessed = DateTime.Now;
|
||||
}
|
||||
|
||||
public void TryUpdateOrCreateRepoCompanyAssignment(RepoAccount repoAccount, TemenosCase temenosCase,
|
||||
Settings settings)
|
||||
{
|
||||
//Temenos may send blank string instead of null
|
||||
var repoCompanyIdFromCase = string.IsNullOrEmpty(temenosCase.RepoAgentId)
|
||||
? null
|
||||
: MapRepoAgentIdToRepoCompanyId(temenosCase.RepoAgentId, settings.RepoCompanies);
|
||||
|
||||
foreach (var collateral in repoAccount.Collaterals)
|
||||
{
|
||||
var mostRecent = collateral.RepoCompanySelections.OrderByDescending(x => x.DateTimeCreated)
|
||||
.FirstOrDefault();
|
||||
|
||||
// TODO, move to Slip, save elsewhere.
|
||||
var updated =
|
||||
UpdateOrCreateRepoCompanyAssignment(collateral, mostRecent, temenosCase, repoCompanyIdFromCase);
|
||||
|
||||
// This can no longer happen, but watch for it anyway.
|
||||
if (updated.Any(repoCompanySelection =>
|
||||
repoCompanySelection.DateRepoed != null &&
|
||||
repoCompanySelection.RepoCompanyAssignmentDate == null))
|
||||
{
|
||||
Log.Error(
|
||||
$"Tried to set DateRepoed before RepoCompanyAssignment for accountNo {repoAccount.AccountNo}. Please Investigate.");
|
||||
throw new MissingRequirementException(
|
||||
$"RepoCompanyAssignment for repo account {repoAccount.AccountNo} Missing RepoCompanyAssignmentDate",
|
||||
$"Cannot assign a DateRepoed before a RepoCompanyAssignment Date.");
|
||||
}
|
||||
|
||||
if (updated.Any())
|
||||
{
|
||||
_repoCompanySelectionService.SaveChangesAsync(updated).GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public List<RepoCompanySelection> UpdateOrCreateRepoCompanyAssignment(Collateral collateral, RepoCompanySelection mostRecentSelection,
|
||||
TemenosCase temenosCase, int? repoCompanyIdFromCase)
|
||||
{
|
||||
var selectionUpdates = new List<RepoCompanySelection>();
|
||||
//If this is the first repoCompanySelection create when temenos has data
|
||||
if (mostRecentSelection == null)
|
||||
{
|
||||
if (temenosCase.RepoAgentId != null && temenosCase.RepoType != null &&
|
||||
temenosCase.DateRepoAssigned != null)
|
||||
{
|
||||
var newRepoCompanyEntry = CreateNewRepoCompanyAssignment(collateral, temenosCase, repoCompanyIdFromCase);
|
||||
collateral.RepoCompanySelections = collateral.RepoCompanySelections.Append(newRepoCompanyEntry).ToList();
|
||||
selectionUpdates.Add(newRepoCompanyEntry);
|
||||
}
|
||||
|
||||
return selectionUpdates;
|
||||
}
|
||||
|
||||
//If the repoCompany has changed update with temenos data
|
||||
if (mostRecentSelection.RepoCompanyId != repoCompanyIdFromCase ||
|
||||
mostRecentSelection.RepoType != temenosCase.RepoType ||
|
||||
mostRecentSelection.RepoCompanyAssignmentDate != temenosCase.DateRepoAssigned)
|
||||
{
|
||||
if (mostRecentSelection.DateRepoed == null)
|
||||
{
|
||||
mostRecentSelection.DateRepoed = DateTime.Now;
|
||||
if (mostRecentSelection.RepoCompanyAssignmentDate == null)
|
||||
{
|
||||
mostRecentSelection.RepoCompanyAssignmentDate = mostRecentSelection.DateTimeCreated ?? DateTime.Now;
|
||||
}
|
||||
|
||||
selectionUpdates.Add(mostRecentSelection);
|
||||
}
|
||||
|
||||
var newRepoCompanyEntry = CreateNewRepoCompanyAssignment(collateral, temenosCase, repoCompanyIdFromCase);
|
||||
collateral.RepoCompanySelections = collateral.RepoCompanySelections.Append(newRepoCompanyEntry).ToList();
|
||||
selectionUpdates.Add(newRepoCompanyEntry);
|
||||
|
||||
return selectionUpdates;
|
||||
}
|
||||
|
||||
//If no need to update or create, sync Repoed Date
|
||||
mostRecentSelection.DateRepoed = temenosCase.DateRepossessed;
|
||||
selectionUpdates.Add(mostRecentSelection);
|
||||
|
||||
return selectionUpdates;
|
||||
}
|
||||
|
||||
public RepoCompanySelection CreateNewRepoCompanyAssignment(Collateral collateral, TemenosCase temenosCase, int? newRepoCompanyId)
|
||||
{
|
||||
// DateRepoAssigned is defaulted to Today. The business (Bill) doesn't want it to throw, if they start having trouble
|
||||
// with the dates; change this to throw a MissingRequirementException on the field.
|
||||
var newRepoCompanySelection = new RepoCompanySelection()
|
||||
{
|
||||
RepoCompanyId = newRepoCompanyId,
|
||||
RepoType = temenosCase.RepoType ?? RepoType.Unknown,
|
||||
RepoCompanyAssignmentDate = temenosCase.DateRepoAssigned ?? DateTime.Now,
|
||||
DateRepoed = temenosCase.DateRepossessed,
|
||||
CollateralId = collateral.CollateralId,
|
||||
DateTimeCreated = DateTime.Now,
|
||||
Id = null
|
||||
};
|
||||
|
||||
return newRepoCompanySelection;
|
||||
}
|
||||
|
||||
public int? MapRepoAgentIdToRepoCompanyId(string repoAgentId, IReadOnlyCollection<RepoCompany> repoCompanies)
|
||||
{
|
||||
try
|
||||
{
|
||||
var repoCompany =
|
||||
repoCompanies.FirstOrDefault(company => company.ReferenceId == int.Parse(repoAgentId));
|
||||
|
||||
if (repoCompany != null)
|
||||
{
|
||||
return repoCompany.ID;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Error when trying to map RepoAgentID: {repoAgentId} to a RepoCompany ReferenceID", repoCompanies);
|
||||
}
|
||||
|
||||
// If you get this error, ensure there is a matching RepoCompany and that it has a RepoAgentID that matches the config call in Collections API.
|
||||
throw new MissingRequirementException("RepoCompanyAssignment - RepoCompanyID", $"Ensure a RepoCompany with RepoAgentID: {repoAgentId} is present");
|
||||
}
|
||||
|
||||
public IResult<DateTime?> GetDateTimeFromString(string dtString)
|
||||
{
|
||||
if (string.IsNullOrEmpty(dtString))
|
||||
{
|
||||
Log.Error("Cannot parse null or empty date string");
|
||||
return new DateTimeResult()
|
||||
{
|
||||
IsSuccess = false,
|
||||
ErrorMessage = "Cannot Parse Empty Date String",
|
||||
};
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return new DateTimeResult()
|
||||
{
|
||||
IsSuccess = true,
|
||||
Value = DateTime.Parse(dtString)
|
||||
};
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Log.Error(ex, $"Cannot Parse Date String {dtString}");
|
||||
return new DateTimeResult()
|
||||
{
|
||||
IsSuccess = false,
|
||||
ErrorMessage = $"There was an Exception when processing the date string: {dtString}"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public int? GetSixtyPlusUserNumber(TemenosAccount account)
|
||||
{
|
||||
try
|
||||
{
|
||||
account.CustomFieldMap.TryGetValue(SixtyPLusUserAssignedLabel, out string sixtyPlusUser);
|
||||
if (String.IsNullOrWhiteSpace(sixtyPlusUser))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Int32.TryParse(sixtyPlusUser, out int sixtyPlusUserNumber);
|
||||
|
||||
return sixtyPlusUserNumber;
|
||||
}
|
||||
catch(NullReferenceException nullEx)
|
||||
{
|
||||
Log.Warning($"60+ Collector key not found in Temenos Account for Account Number: {account.AccountNumber}. Error: {nullEx}");
|
||||
return null;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Log.Error($"Error while attempting to Parse 60+ Collector ID for Account Number: {account.AccountNumber}. Error: {ex}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user