Adding
This commit is contained in:
+38
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+51
@@ -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}";
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+64
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+11
@@ -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);
|
||||
}
|
||||
}
|
||||
+52
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+118
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+56
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+36
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+68
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+53
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+71
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+19
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
+42
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+55
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+35
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+38
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+50
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+95
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+73
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+84
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+21
@@ -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()
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -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
|
||||
|
||||
}
|
||||
}
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace LCU.API.Collections.RepoApi.Domain.FloorPricing
|
||||
{
|
||||
public interface IFloorPricingStrategyFactory
|
||||
{
|
||||
IReadOnlyCollection<IFloorPricingStrategy> GetFloorPricingStrategies();
|
||||
}
|
||||
}
|
||||
+43
@@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
+144
@@ -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.
|
||||
}
|
||||
}
|
||||
}
|
||||
+13
@@ -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);
|
||||
}
|
||||
}
|
||||
+26
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+96
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+41
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user