This commit is contained in:
Zeeshaun
2026-04-20 14:47:24 -05:00
parent 89057eceea
commit 8b3ea92501
485 changed files with 71042 additions and 0 deletions
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class AuctionOnHoldChecker : IAlertChecker
{
private const string RehoStatus = "REHO";
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
var newEventAlerts = new List<EventAlert>();
foreach (var auctionAssignment in slip.AuctionAssignments)
{
if (
slip.UpdatedRepoAccount.Status == RehoStatus
&& auctionAssignment?.HoldStatus != null
&& auctionAssignment.HoldStatus > 0)
{
newEventAlerts.Add(
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = "Auction On Hold",
CreatedBy = "Batch",
DateAdded = DateTime.Now
}
);
}
}
return newEventAlerts.Count > 0 ? newEventAlerts : null;
}
}
}
@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.API.Collections.RepoApi.Domain.Extensions;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.API.Collections.RepoApi.Extensions;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class AuctionVinDifferentChecker : IAlertChecker
{
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
var newEventAlerts = new List<EventAlert>();
var repoManagerVins = slip.UpdatedRepoAccount.GetCollateralVinList();
foreach (var auctionAssignment in slip.AuctionAssignments)
{
if (auctionAssignment?.AutoIMS?.VIN != null
&& !repoManagerVins.Contains(auctionAssignment.AutoIMS.VIN.Trim().ToUpper()))
{
newEventAlerts.Add(
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = "Auction VIN Different",
SubMessage = FormatSubMessage(auctionAssignment.AutoIMS.VIN.Trim().ToUpper(), repoManagerVins),
CreatedBy = "Batch",
DateAdded = DateTime.Now
}
);
}
}
return newEventAlerts.Count > 0 ? newEventAlerts : null;
}
private static string FormatSubMessage(string auctionAssignmentVin, IReadOnlyCollection<string> vins)
{
if (vins.Count() == 1)
{
return
$"Auction Assignment VIN: {auctionAssignmentVin} differs from RepoManager VIN: {vins.First()}";
}
var repoVins = vins.Aggregate((x, y) => x + ", " + y);
return $"Auction Assignment VIN: {auctionAssignmentVin} is not among RepoManager VINs: {repoVins}";
}
}
}
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class CreditAdjustmentChecker : TransactionHistoryChecker
{
private readonly List<string> _watchedStatuses = new List<string> { "O4R", "REPO", "SOLD" };
private readonly List<string> _watchedTransactionTypes = new List<string> { "PDSB", "RPMT" };
private const string Message = "Credit Adjustment Received";
public override List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
return StandardizedCheckForAlert(new AlertProcessingSlip(slip, Message, _watchedStatuses, _watchedTransactionTypes));
}
}
}
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class DebitAdjustmentChecker : TransactionHistoryChecker
{
private readonly List<string> _watchedStatuses = new List<string> { "O4R", "REPO", "SOLD" };
private readonly List<string> _watchedTransactionTypes = new List<string> { "CHST", "PRCT" };
private const string Message = "Debit Adjustment Received";
public override List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
return StandardizedCheckForAlert(new AlertProcessingSlip(slip, Message, _watchedStatuses, _watchedTransactionTypes));
}
}
}
@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class DocumentNotFoundChecker : IAlertChecker
{
private readonly string _document;
private readonly string _ilActionCode;
private readonly string _status;
private readonly string _wiActionCode;
private const string StateWisconsin = "WI";
private const string StateIllinois = "IL";
public DocumentNotFoundChecker(string document, string wiActionCode, string ilActionCode = null,
string status = null)
{
_document = document;
_wiActionCode = wiActionCode.Trim().ToUpper();
_ilActionCode = ilActionCode?.Trim().ToUpper();
_status = status?.Trim().ToUpper();
}
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
if (slip.Letters == null || slip.Member?.Address == null ||
(_status != null && slip.UpdatedRepoAccount.Status != _status ||
slip.UpdatedRepoAccount.Bankruptcy != null))
{
return null;
}
var isLetterMissing = false;
if (slip.Member.Address.State == StateWisconsin)
{
isLetterMissing = slip.Letters.ToList().Find(x => x.ActionCode == _wiActionCode) == null;
}
else if (_ilActionCode != null && slip.Member.Address.State == StateIllinois)
{
isLetterMissing = slip.Letters.ToList().Find(x => x.ActionCode == _ilActionCode) == null;
}
if (isLetterMissing)
{
return new List<EventAlert>
{
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = _document + " Not Found",
CreatedBy = "Batch",
DateAdded = DateTime.Now
}
};
}
return null;
}
}
}
@@ -0,0 +1,11 @@
using System.Collections.Generic;
using LCU.API.Collections.RepoApi.Domain.Processing;
using EventAlert = LCU.Shared.Collections.RepoApiLib.Models.EventAlert;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public interface IAlertChecker
{
List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip);
}
}
@@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class NoticeOfSurplusDeficiencyNotSentChecker : IAlertChecker
{
private const int OwnershipTypeImportant = 6;
private const int DaysBeforeConsideredNotSent = 2;
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
var updatedRepoAccount = slip.UpdatedRepoAccount;
if (updatedRepoAccount.RefundStatus?.RefundsReceivedDate == null || updatedRepoAccount.DateClosed != null)
{
return null;
}
var letterSentBy = DateTime.Now.AddDays(-DaysBeforeConsideredNotSent).Date;
if (updatedRepoAccount.RefundStatus.RefundsReceivedDate < letterSentBy && updatedRepoAccount.Bankruptcy == null)
{
return new List<EventAlert>
{
new EventAlert()
{
RepoId = updatedRepoAccount.RepoId,
AccountNo = updatedRepoAccount.AccountNo,
Message = "Notice of Surplus Deficiency Letter Not Sent",
CreatedBy = "Batch",
DateAdded = DateTime.Now,
EmailRecipient = "[email protected]",
EmailBody = CreateEmailBody(updatedRepoAccount)
}
};
}
return null;
}
private string CreateEmailBody(RepoAccount repoAccount)
{
var message = "Notice of Surplus Deficiency Not Sent.<br/><br/>";
var member = repoAccount.Borrowers?.First(b => b.OwnershipType == OwnershipTypeImportant);
var memberName = "Member Name: " + member?.IndividualName + "<br/>";
var accountNo = "Account No: " + repoAccount.AccountNo + "<br/>";
return string.Concat(message, memberName, accountNo);
}
}
}
@@ -0,0 +1,118 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.API.Collections.CollectionsApiLib.Models;
using LCU.API.Collections.RepoApi.Domain.Extensions;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.API.Collections.RepoApi.Extensions;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class O4rNoRepoChecker : IAlertChecker
{
private readonly RepoType _repoType;
private const string O4RStatus = "O4R";
private const string EmailDestination = "[email protected]";
public O4rNoRepoChecker(RepoType repoType)
{
_repoType = repoType;
}
private int GetDayCount()
{
switch (_repoType)
{
case RepoType.Voluntary: return 2;
case RepoType.Involuntary: return 60;
case RepoType.PreSkip: return 31;
default: return -1;
}
}
private int GetRepeatDayCount()
{
switch (_repoType)
{
case RepoType.Voluntary: return 1;
case RepoType.Involuntary: return 30;
default: return 0;
}
}
public bool ShouldSendRepeatedAlert(int daysInStatus, int dayCount, int repeatCount)
{
return (
daysInStatus > dayCount
&& (repeatCount > 0)
&& ((daysInStatus - dayCount) % repeatCount == 0)
);
}
public bool IsRepoAssigned(RepoAccount repoAccount)
{
foreach (var collateral in repoAccount.Collaterals)
{
if (collateral.RepoCompanySelections?.FirstOrDefault(x => x.RepoCompanyAssignmentDate != null && x.RepoType == _repoType) != null)
{
return true;
}
}
return false;
}
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
var daysInStatus = slip.UpdatedRepoAccount.GetDaysInStatus(O4RStatus);
if (daysInStatus > 0 && slip.UpdatedRepoAccount.Status == O4RStatus)
{
// In order to reduce duplicate notifications, do not create a notification if no repo is assigned.
// An 'action needed' item will be created for O4R No Repo if no repo is assigned. A notification is redundant.
if (daysInStatus < GetDayCount() || !IsRepoAssigned(slip.UpdatedRepoAccount))
{
return null;
}
if (daysInStatus == GetDayCount() || ShouldSendRepeatedAlert(daysInStatus, GetDayCount(), GetRepeatDayCount()))
{
return new List<EventAlert>
{
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = "O4R No Repo",
CreatedBy = "Batch",
DateAdded = DateTime.Now,
EmailRecipient = _repoType == RepoType.PreSkip
? EmailDestination
: null,
EmailBody = _repoType == RepoType.PreSkip
? CreateEmailBody(slip.UpdatedRepoAccount)
: null
}
};
}
}
return null;
}
private string CreateEmailBody(RepoAccount repoAccount)
{
var message = "PreSkip has ended, please file a Skip Claim.<br/><br/>";
var member = repoAccount.Borrowers?.First(b => b.OwnershipType == 6);
var memberName = "Member Name: " + member?.IndividualName + "<br/>";
var accountNo = "Account No: " + repoAccount.AccountNo + "<br/>";
var collateral = repoAccount.Collaterals.FirstOrDefault(c => c.RepoCompanySelections?.FirstOrDefault(x => x.RepoType == _repoType) != null);
var year = "Year: " + collateral.Year + "<br/>";
var make = "Make: " + collateral.Make + "<br/>";
var model = "Model: " + collateral.Model + "<br/>";
var vin = "VIN: " + collateral.Vin + "<br/>";
return string.Concat(message, memberName, accountNo, year, make, model, vin);
}
}
}
@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
using LCU.Shared.Collections.RepoApiLib.Models.Consumer;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class PaymentReceivedChecker : TransactionHistoryChecker
{
private readonly List<string> _watchedStatuses = new List<string> { "O4R", "REPO", "SOLD" };
private readonly List<string> _watchedTransactionTypes = new List<string> { "COFR", "POFF", "EXT", "SPMT", "XPMT" };
private const string EmailDestination = "[email protected]";
public override List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
var newEventAlerts = new List<EventAlert>();
var results = GetRecentTransactionsFromType(_watchedTransactionTypes, slip.TransactionHistory);
if (results != null && _watchedStatuses.Contains(slip.UpdatedRepoAccount.Status))
{
var dueDate = slip.Loan?.NextDueDate.ToString(CultureInfo.CurrentCulture) ?? "Unknown";
foreach (var result in results)
{
newEventAlerts.Add(new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = "Payment Received",
SubMessage = FormatTransactionMessage(result),
CreatedBy = "Batch",
DateAdded = DateTime.Now,
EmailRecipient = EmailDestination,
EmailBody = CreateEmailBody(slip.UpdatedRepoAccount, result, dueDate)
});
}
}
return newEventAlerts.Count > 0 ? newEventAlerts : null;
}
private string CreateEmailBody(RepoAccount repoAccount, CoreTransaction coreTransaction, string nextDueDate)
{
var message = "Loan Payment<br/><br/>";
var member = repoAccount.Borrowers?.First(b => b.OwnershipType == 6);
var memberName = "Member Name: " + member?.IndividualName + "<br/>";
var accountNo = "Account No: " + repoAccount.AccountNo + "<br/>";
var paymentAmount = "Payment Amount: " + coreTransaction.amount + "<br/>";
var nextDueDateString = "Next Due Date: " + nextDueDate + "<br/>";
var loanBalance = "Loan Balance: " + coreTransaction.runningBalance + "<br/>";
return string.Concat(message, memberName, accountNo, paymentAmount, nextDueDateString, loanBalance);
}
}
}
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class RedemptionEndedNoTitleOrderedChecker : IAlertChecker
{
private const string RepoStatus = "REPO";
private const int RedemptionExpiredDaysToWait = 7;
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
if (slip.UpdatedRepoAccount.Status.ToUpper() == RepoStatus &&
DateTime.Now.Date > slip.UpdatedRepoAccount.RedemptionExpiredDate?.AddDays(RedemptionExpiredDaysToWait) &&
slip.UpdatedRepoAccount.Collaterals.FirstOrDefault(x => x.RetitledInLandmarkName == null) != null)
{
return new List<EventAlert>
{
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = "Redemption Ended No Title Ordered",
CreatedBy = "Batch",
DateAdded = DateTime.Now
}
};
}
return null;
}
}
}
@@ -0,0 +1,68 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.API.Collections.RepoApi.Domain.Extensions;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.API.Collections.RepoApi.Extensions;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class RepoNotMovedChecker : IAlertChecker
{
private readonly int _dayCount;
private const string RepoStatus = "REPO";
private const int DaysBeforeSendEmail = 14;
private const string EmailDestination = "[email protected]";
public RepoNotMovedChecker(int dayCount = 30)
{
_dayCount = dayCount;
}
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
var daysInStatus = slip.UpdatedRepoAccount.GetDaysInStatus(RepoStatus);
if (slip.UpdatedRepoAccount.Status.ToUpper() != RepoStatus ||
slip.UpdatedRepoAccount.Collaterals == null || !slip.UpdatedRepoAccount.Collaterals.Any()
|| daysInStatus < _dayCount)
{
return null;
}
foreach (var collateral in slip.UpdatedRepoAccount.Collaterals)
{
if (collateral.AuctionAssignmentToCollaterals != null &&
collateral.AuctionAssignmentToCollaterals.Any())
{
var assignment = collateral.AuctionAssignmentToCollaterals
.Where(x => x.AuctionAssignment != null && x.AuctionAssignment.DateSubmitted == null)
.ToList();
if (assignment.Any() && daysInStatus >= _dayCount)
{
return new List<EventAlert>
{
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = "Repo Not Moved",
CreatedBy = "Batch",
DateAdded = DateTime.Now,
EmailRecipient = daysInStatus == DaysBeforeSendEmail
? EmailDestination
: null,
EmailBody = daysInStatus == DaysBeforeSendEmail
? string.Format("{0} Account status REPO, Not Moved for extended time.", slip.UpdatedRepoAccount.AccountNo)
: null
}
};
}
}
}
return null;
}
}
}
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.API.Collections.RepoApi.Domain.Extensions;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.API.Collections.RepoApi.Extensions;
using EventAlert = LCU.Shared.Collections.RepoApiLib.Models.EventAlert;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class RepoNotSoldChecker : IAlertChecker
{
private readonly int _dayCount;
private readonly int _repeatDayCount;
private const string RepoStatus = "REPO";
private const string DestinationEmail = "[email protected]";
public RepoNotSoldChecker(int dayCount = 30, int repeatDayCount = 0)
{
_dayCount = dayCount;
_repeatDayCount = repeatDayCount;
}
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
var status = slip.UpdatedRepoAccount.Status.ToUpper().Trim();
var days = slip.UpdatedRepoAccount.GetDaysInStatus(status);
if (status == RepoStatus && days > 0)
{
if ((days == _dayCount) || days >= _dayCount && ((days - _dayCount) % _repeatDayCount == 0))
{
var member = slip.UpdatedRepoAccount.Borrowers?.First(b => b.OwnershipType == 6);
return new List<EventAlert>
{
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = "Repo Not Sold",
CreatedBy = "Batch",
DateAdded = DateTime.Now,
EmailRecipient = DestinationEmail,
EmailBody = string.Format("{0} {1} Account status REPO, Not Sold for extended time.",
slip.UpdatedRepoAccount.AccountNo, member?.IndividualName)
}
};
}
}
return null;
}
}
}
@@ -0,0 +1,71 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.API.Collections.CollectionsApiLib.Models;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class RtcLetterNotFoundChecker : IAlertChecker
{
private readonly string _document;
private readonly IEnumerable<string> _actionCodes;
private const string O4RStatus = "O4R";
private const string StateWisconsin = "WI";
public RtcLetterNotFoundChecker(string document, IEnumerable<string> actionCodes)
{
_document = document;
_actionCodes = actionCodes.Select(code => code.Trim().ToUpper());
}
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
if (slip.Member?.Address == null || slip.Member.Address.State != StateWisconsin || slip.Letters == null)
{
return null;
}
foreach (var collateral in slip.UpdatedRepoAccount.Collaterals)
{
if (collateral.RepoCompanySelections?.FirstOrDefault(x => x.RepoType == RepoType.Voluntary) != null)
{
return null;
}
}
var letters = slip.Letters;
if (IsLetterMissing(letters) && (slip.UpdatedRepoAccount.Status.ToUpper().Trim() == O4RStatus))
{
return new List<EventAlert>
{
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = _document + " Not Found",
CreatedBy = "Batch",
DateAdded = DateTime.Now
}
};
}
return null;
}
private bool IsLetterMissing(IEnumerable<RepoLetter> letters)
{
var sentLetterCodes = letters.Select(x => x.ActionCode).ToList();
foreach (var actionCode in _actionCodes)
{
if (sentLetterCodes.Contains(actionCode))
{
return false;
}
}
return true;
}
}
}
@@ -0,0 +1,19 @@
using System;
using System.Collections.Generic;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class SaleFundsReceivedChecker : TransactionHistoryChecker
{
private readonly List<string> _watchedStatuses = null;
private readonly List<string> _watchedTransactionTypes = new List<string> { "PRCT" };
private const string Message = "Sale Funds Received";
public override List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
return StandardizedCheckForAlert(new AlertProcessingSlip(slip, Message, _watchedStatuses, _watchedTransactionTypes));
}
}
}
@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using LCU.API.Collections.RepoApi.Domain.Extensions;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.API.Collections.RepoApi.Extensions;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class SkipEndChecker : IAlertChecker
{
private readonly int _dayCount;
private const string O4RStatus = "O4R";
public SkipEndChecker(int dayCount = 30)
{
_dayCount = dayCount;
}
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
if (slip.UpdatedRepoAccount.Status == O4RStatus
&& slip.UpdatedRepoAccount.GetDaysInStatus(O4RStatus) >= _dayCount
&& slip.UpdatedRepoAccount.IsCurrentlySkipClaim())
{
return new List<EventAlert>
{
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = "Skip Claim Ended",
CreatedBy = "Batch",
DateAdded = DateTime.Now
}
};
}
return null;
}
}
}
@@ -0,0 +1,55 @@
using System;
using System.Collections.Generic;
using LCU.API.Collections.RepoApi.Domain.Extensions;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.API.Collections.RepoApi.Extensions;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class SkipNotPaidChecker : IAlertChecker
{
private readonly int _dayCount;
private readonly int _repeatDayCount;
private const string O4RStatus = "O4R";
private const string RepoSubStatusPaidSkip = "PSKP";
public SkipNotPaidChecker(int dayCount = 30, int repeatDayCount = 0)
{
_dayCount = dayCount;
_repeatDayCount = repeatDayCount;
}
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
var days = slip.UpdatedRepoAccount.GetDaysInStatus(O4RStatus);
if (slip.UpdatedRepoAccount.Status == O4RStatus
&& days >= _dayCount && slip.UpdatedRepoAccount.IsCurrentlySkipClaim())
{
if (!string.IsNullOrWhiteSpace(slip.UpdatedRepoAccount.SubStatus) &&
slip.UpdatedRepoAccount.SubStatus.Trim().ToUpper() == RepoSubStatusPaidSkip)
{
return null;
}
if ((days == _dayCount) || days >= _dayCount && ((days - _dayCount) % _repeatDayCount == 0))
{
return new List<EventAlert>
{
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = "Skip Not Paid",
CreatedBy = "Batch",
DateAdded = DateTime.Now
}
};
}
}
return null;
}
}
}
@@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class SkipPaidChecker : IAlertChecker
{
public const string RepoSubStatusPaidSkip = "PSKP";
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
if (slip.UpdatedRepoAccount != null && !string.IsNullOrWhiteSpace(slip.UpdatedRepoAccount.SubStatus) &&
slip.UpdatedRepoAccount.SubStatus.Trim().ToUpper() == RepoSubStatusPaidSkip &&
slip.UpdatedRepoAccount.Status.ToUpper().Trim() != RepoSubStatusPaidSkip)
{
slip.UpdatedRepoAccount.Status = RepoSubStatusPaidSkip; // TODO Does this get saved? Is it ok to do? (NO)
return new List<EventAlert>
{
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = "Skip Claim Paid",
CreatedBy = "Batch",
DateAdded = DateTime.Now
}
};
}
return null;
}
}
}
@@ -0,0 +1,38 @@
using System;
using System.Collections.Generic;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class SoldNoProceedsChecker : IAlertChecker
{
private const int DaysToWaitForSold = 5;
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
foreach (var auctionAssignment in slip.AuctionAssignments)
{
if (
auctionAssignment?.AutoIMS?.SoldDate != null
&& auctionAssignment.AutoIMS.SoldDate < DateTime.Now.AddDays(-DaysToWaitForSold)
&& auctionAssignment.AutoIMS.FundsSent == null)
{
return new List<EventAlert>
{
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = "Sold No Proceeds",
CreatedBy = "Batch",
DateAdded = DateTime.Now
}
};
}
}
return null;
}
}
}
@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class StatusChangeChecker : IAlertChecker
{
private readonly string _watchStatus;
private readonly IEnumerable<string> _fromStatusList;
private const string NoStatus = "";
public StatusChangeChecker(string watchStatus, IEnumerable<string> fromStatusList = null)
{
_watchStatus = watchStatus.Trim().ToUpper();
_fromStatusList = fromStatusList;
}
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
var originalStatus = slip.ExistingRepoAccount?.Status.ToUpper().Trim();
var updatedStatus = slip.UpdatedRepoAccount.Status.ToUpper().Trim();
// new account.
if (originalStatus == null)
{
originalStatus = NoStatus;
}
if (updatedStatus == _watchStatus
&& originalStatus != _watchStatus
&& (_fromStatusList == null || _fromStatusList.Contains(originalStatus)))
{
return new List<EventAlert>
{
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = "Status Changed to " + _watchStatus,
SubMessage =
$"Previously {originalStatus}. I confirm that I have updated the Status to {updatedStatus} in RDN.",
CreatedBy = "Batch",
DateAdded = DateTime.Now
}
};
}
return null;
}
}
}
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using LCU.API.Collections.RepoApi.Domain.Extensions;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.API.Collections.RepoApi.Extensions;
using EventAlert = LCU.Shared.Collections.RepoApiLib.Models.EventAlert;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public class TimeInStatusChecker : IAlertChecker
{
private readonly string _checkStatus;
private readonly int _dayCount;
private readonly int _repeatDayCount;
public TimeInStatusChecker(string checkStatus, int dayCount = 30, int repeatDayCount = 0)
{
_checkStatus = checkStatus;
_dayCount = dayCount;
_repeatDayCount = repeatDayCount;
}
public List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip)
{
var status = slip.UpdatedRepoAccount.Status.ToUpper().Trim();
var days = slip.UpdatedRepoAccount.GetDaysInStatus(status);
if (days <= 0 || status != _checkStatus)
{
return null;
}
if ((days == _dayCount) || days >= _dayCount && ((days - _dayCount) % _repeatDayCount == 0))
{
return new List<EventAlert>
{
new EventAlert()
{
RepoId = slip.UpdatedRepoAccount.RepoId,
AccountNo = slip.UpdatedRepoAccount.AccountNo,
Message = days + " days in " + _checkStatus,
CreatedBy = "Batch",
DateAdded = DateTime.Now
}
};
}
return null;
}
}
}
@@ -0,0 +1,95 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.API.Collections.RepoApi.Domain.Processing;
using LCU.Shared.Collections.RepoApiLib.Models;
using LCU.Shared.Collections.RepoApiLib.Models.Consumer;
namespace LCU.API.Collections.RepoApi.Domain.AlertChecker
{
public abstract class TransactionHistoryChecker : IAlertChecker
{
private readonly int _daysInPastToSearch;
protected TransactionHistoryChecker(int daysInPastToSearch = 3)
{
_daysInPastToSearch = daysInPastToSearch;
}
public abstract List<EventAlert> CheckForAlert(RepoAccountProcessingSlip slip);
protected List<EventAlert> StandardizedCheckForAlert(AlertProcessingSlip alertProcessingSlip)
{
var newEventAlerts = new List<EventAlert>();
var results = GetRecentTransactionsFromType
(
alertProcessingSlip.WatchedTransactionTypes,
alertProcessingSlip.AccountProcessingSlip.TransactionHistory
);
if (results != null && AccountStatusIsQualified(alertProcessingSlip.WatchedStatusTypes,
alertProcessingSlip.AccountProcessingSlip.UpdatedRepoAccount.Status))
{
foreach (var result in results)
{
newEventAlerts.Add(new EventAlert()
{
RepoId = alertProcessingSlip.AccountProcessingSlip.UpdatedRepoAccount.RepoId,
AccountNo = alertProcessingSlip.AccountProcessingSlip.UpdatedRepoAccount.AccountNo,
Message = alertProcessingSlip.Message,
SubMessage = FormatTransactionMessage(result),
CreatedBy = "Batch",
DateAdded = DateTime.Now
}
);
}
}
return newEventAlerts.Count > 0 ? newEventAlerts : null;
}
private static bool AccountStatusIsQualified(List<string> watchedStatusTypes, string status)
{
return watchedStatusTypes?.Contains(status.ToUpper()) ?? true;
}
public static string FormatTransactionMessage(CoreTransaction transaction)
{
var message = "";
if (transaction.transactionTypeCode.ToLower().Contains("undo"))
{
message += "***TRANSACTION UNDONE*** - ";
}
return message +
$"Transaction Type: \"{transaction.transactionTypeCode}\" | Posting: {transaction.postingDate} | Transaction: {transaction.amount?.ToString("C2")} | Balance: {transaction.runningBalance?.ToString("C2")} | Description: \"{transaction.transactionTypeDescription}\"";
}
protected List<CoreTransaction> GetRecentTransactionsFromType(List<string> transactionTypes, IReadOnlyCollection<CoreTransaction> transactions)
{
var returnItems = new List<CoreTransaction>();
if (transactions != null && transactions.Any())
{
foreach (var transactionType in transactionTypes)
{
returnItems.AddRange(GetRecentTransactionsFromType(transactionType, _daysInPastToSearch, transactions));
}
}
return returnItems.Count > 0 ? returnItems : null;
}
private static IEnumerable<CoreTransaction> GetRecentTransactionsFromType(string transactionType, int daysInPast, IReadOnlyCollection<CoreTransaction> transactions)
{
return (
from transaction in transactions
where (
transaction.postingDate > DateTime.Now.AddDays(-daysInPast)
&& transaction.transactionTypeCode.ToLower().Contains(transactionType.ToLower()
)
)
select transaction
).ToList();
}
}
}
@@ -0,0 +1,73 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.API.Collections.CollectionsApiLib.Models;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.Extensions
{
public static class RepoAccountExtensions
{
public static bool HasAnyCollateral(this RepoAccount repoAccount)
{
return repoAccount?.Collaterals != null && repoAccount.Collaterals.Any();
}
public static IReadOnlyCollection<string> GetCollateralVinList(this RepoAccount repoAccount)
{
if (repoAccount?.Collaterals != null && repoAccount.Collaterals.Any())
{
return repoAccount.Collaterals.Select(x => x.Vin?.Trim().ToUpper()).ToList();
}
return new List<string>();
}
public static IReadOnlyCollection<int> GetUniqueAuctionAssignmentIds(this RepoAccount repoAccount)
{
var aaList = new List<int>();
if (repoAccount?.Collaterals == null || !repoAccount.Collaterals.Any()) return aaList;
foreach (var collateral in repoAccount.Collaterals)
{
if (collateral.AuctionAssignmentToCollaterals?.Any() != true) continue;
foreach (var toCollateral in collateral.AuctionAssignmentToCollaterals)
{
aaList.Add(toCollateral.AuctionAssignmentId);
}
}
return aaList;
}
public static int GetDaysInStatus(this RepoAccount repoAccount, string status)
{
if (repoAccount?.RepoStatusChanges == null || !repoAccount.RepoStatusChanges.Any())
{
return -1;
}
var lastStatus = repoAccount.RepoStatusChanges.LastOrDefault(x => x.StatusTo == status);
if (lastStatus != null)
{
return (DateTime.Now - lastStatus.ChangedDate).Days;
}
return -1;
}
public static bool IsCurrentlySkipClaim(this RepoAccount updatedRepoAccount)
{
foreach (var collateral in updatedRepoAccount.Collaterals)
{
if (collateral.RepoCompanySelections == null || !collateral.RepoCompanySelections.Any()) continue;
if (collateral.RepoCompanySelections?.OrderByDescending(x => x.DateTimeCreated).First().RepoType ==
RepoType.Skip)
{
return true;
}
}
return false;
}
}
}
@@ -0,0 +1,84 @@
using System.Linq;
using LCU.API.Collections.CollectionsApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.Extensions
{
public static class TemenosAccountExtensions
{
public const string UserStatusFieldOld = "User Status Code 1";
public const int OpenCaseStatus = 33;
public const int CaseTypeRepossession = 7;
public static bool HasOldIntellectStatus(this TemenosAccount recoveryAccount)
{
return recoveryAccount?.CustomFieldMap != null
&& recoveryAccount.CustomFieldMap.ContainsKey(UserStatusFieldOld)
&& !string.IsNullOrWhiteSpace(recoveryAccount.CustomFieldMap[UserStatusFieldOld]);
}
/**
* CaseId of 0 corresponds to "An open case with a valid RepoStatus"
*/
public static bool HasRecoveryCase(this TemenosAccount recoveryAccount, int caseId)
{
if (recoveryAccount?.Cases == null || !recoveryAccount.Cases.Any())
{
return false;
}
return caseId == 0 ? recoveryAccount.HasOpenRecoveryCase() : recoveryAccount.HasSpecificCase(caseId);
}
public static bool HasOpenRecoveryCase(this TemenosAccount recoveryAccount)
{
TemenosCase selectedCase = null;
if (recoveryAccount?.Cases == null || !recoveryAccount.Cases.Any())
{
return false;
}
selectedCase = recoveryAccount.Cases
.FirstOrDefault(x => x.CaseStatusId == OpenCaseStatus && x.RepoStatus != RepoStatus.UNKNOWN);
return (selectedCase != null);
}
public static bool HasSpecificCase(this TemenosAccount recoveryAccount, int caseId)
{
TemenosCase selectedCase = null;
if (recoveryAccount?.Cases == null || !recoveryAccount.Cases.Any())
{
return false;
}
selectedCase = recoveryAccount.Cases.FirstOrDefault(x => x.CaseId == caseId);
return (selectedCase != null);
}
public static TemenosCase FindRecoveryCaseOrDefault(this TemenosAccount recoveryAccount, int caseId)
{
TemenosCase selectedCase = null;
if (recoveryAccount?.Cases == null || !recoveryAccount.Cases.Any())
{
return null;
}
if (caseId > 0)
{
selectedCase = recoveryAccount.Cases.FirstOrDefault(x => x.CaseId == caseId);
}
else
{
selectedCase = recoveryAccount.Cases
.Where(x => x.CaseStatusId == OpenCaseStatus && x.RepoStatus != RepoStatus.UNKNOWN
&& x.CaseTypeId == CaseTypeRepossession && x.RepoStatus != RepoStatus.INELIGIBLE)
.OrderByDescending(x => x.CreatedDate).FirstOrDefault();
}
return selectedCase;
}
}
}
@@ -0,0 +1,21 @@
using System.Collections.Generic;
using LCU.API.Collections.RepoApi.Domain.FloorPricing.Strategies;
namespace LCU.API.Collections.RepoApi.Domain.FloorPricing
{
public class FloorPricingStrategyFactory : IFloorPricingStrategyFactory
{
public FloorPricingStrategyFactory()
{
}
public IReadOnlyCollection<IFloorPricingStrategy> GetFloorPricingStrategies()
{
return new List<IFloorPricingStrategy>()
{
new AutoImsSuggestedValue(),
new NadaMmrConditionValue()
};
}
}
}
@@ -0,0 +1,13 @@
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.FloorPricing
{
public interface IFloorPricingStrategy
{
FloorPriceCalculationResult CalculateFloorPrice(FloorPriceCalculationParams fpcParams);
int GetStrategyWeight();
// Requirements and Qualifications
}
}
@@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace LCU.API.Collections.RepoApi.Domain.FloorPricing
{
public interface IFloorPricingStrategyFactory
{
IReadOnlyCollection<IFloorPricingStrategy> GetFloorPricingStrategies();
}
}
@@ -0,0 +1,43 @@
using System;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Domain.FloorPricing.Strategies
{
/**
* This strategy uses the suggested price from the ImsAutoFileData entry for the auction.
*/
public class AutoImsSuggestedValue : IFloorPricingStrategy
{
public AutoImsSuggestedValue() { }
public FloorPriceCalculationResult CalculateFloorPrice(FloorPriceCalculationParams fpcParams)
{
var result = new FloorPriceCalculationResult()
{
StrategyName = this.GetType().FullName,
Success = false
};
if (fpcParams?.ImsAutoFileData?.AuctionGuideBookPrice != null)
{
// Price must be rounded to nearest $5 per business.
var inputPrice = (double)(fpcParams.ImsAutoFileData.AuctionGuideBookPrice ?? 0);
result.FloorPrice = (decimal) (Math.Round( inputPrice / 5.0) * 5);
result.Success = result.FloorPrice > 0;
result.FailureReason = result.Success ? "" : "Failed attempt to use auction guide book price.";
}
else
{
result.Success = false;
result.FailureReason = "Auction Guide Book Price missing.";
}
return result;
}
public int GetStrategyWeight()
{
return 0; // This is NOT the preferred strategy.
}
}
}
@@ -0,0 +1,144 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.Shared.Collections.RepoApiLib.Models;
using LCU.Shared.Collections.RepoApiLib.WebServices.NADA;
using Microsoft.EntityFrameworkCore.Internal;
using Enumerable = System.Linq.Enumerable;
namespace LCU.API.Collections.RepoApi.Domain.FloorPricing.Strategies
{
/**
* This strategy uses the AutoIms MMR values, the values from Nada, and the condition from AutoIms along
* with a specific formula, to calculate a floor.
*/
public class NadaMmrConditionValue : IFloorPricingStrategy
{
public FloorPriceCalculationResult CalculateFloorPrice(FloorPriceCalculationParams fpcParams)
{
var result = new FloorPriceCalculationResult()
{
StrategyName = this.GetType().FullName,
Success = false,
ResultWeight = -1
};
if (fpcParams?.CollateralValuation == null || fpcParams?.ImsAutoFileData == null ||
fpcParams?.VehicleFloorPricingParameters == null
|| fpcParams?.VehicleFloorPricingParameters?.Any() == false)
{
// May want to log here, or set a reason code on the result.
result.FailureReason = "Missing ImsAuto File, Pricing Parameters, or Collateral Valuation";
return result;
}
// 1. use condition to find the correct params.
// + VehicleDemicalGradeCode is ignored, ApprovedVehicleGrade is preferred, and Vehicle Grade is the fallback.
var condition = fpcParams.ImsAutoFileData.ApprovedVehicleGrade ?? fpcParams.ImsAutoFileData.VehicleGrade;
if (condition <= 0 || condition == null)
{
result.FailureReason = "No Condition In AutoIMS File Data";
return result;
}
// 2. Get the matching floor pricing calculation row.
var floorPricing = Enumerable.First(Enumerable.Where(fpcParams.VehicleFloorPricingParameters,
x => condition >= x.LowCondition && condition <= x.HighCondition));
// 3. Get the floor pricing params based on matching condition.
var mmrValue = GetMMRValueByParam(floorPricing.MMRAdjustedValue, fpcParams.ImsAutoFileData,
fpcParams.ImsAutoFileData.AuctionGuideBookPrice ?? 0);
List<Decimal> nadaValues = new List<decimal>();
nadaValues.Add(GetNadaValueByParam(floorPricing.NADAFirstValue, fpcParams.CollateralValuation));
nadaValues.Add(GetNadaValueByParam(floorPricing.NADASecondValue, fpcParams.CollateralValuation));
bool onlyZeroNadaValues = nadaValues.TrueForAll(x => x == 0);
if (fpcParams.CollateralValuation.Year <= (DateTime.Now.AddYears(-7).Year) || onlyZeroNadaValues)
{
nadaValues.Add(fpcParams.CollateralValuation.TradeIn);
onlyZeroNadaValues = nadaValues.TrueForAll(x => x == 0);
}
// If there are no NADA or MMR prices, fail the strategy
if (onlyZeroNadaValues && (mmrValue == 0 || mmrValue == fpcParams.ImsAutoFileData.AuctionGuideBookPrice))
{
result.FailureReason = "No NADA or MMR prices returned";
return result;
}
// 4. Run the formula
var nadaValue = GetNadaAvgValue(nadaValues);
var divisor = mmrValue == 0 || nadaValue == 0
? 1
: 2;
var baseResult = (mmrValue + nadaValue) / divisor;
baseResult = (baseResult * floorPricing.PercentageOf) / 100;
baseResult = Math.Round(baseResult / 5.0m) * 5;
result.FloorPrice = baseResult;
result.Success = true;
result.ResultWeight = GetStrategyWeight();
return result;
}
public decimal GetNadaAvgValue(List<Decimal> nadaValues)
{
Decimal aggregateValue = 0;
int valueCount = 0;
Decimal aggregateAvgValue = 0;
if (nadaValues.Count() > 0)
{
foreach (var nadaVal in nadaValues)
{
if (nadaVal > 0)
{
aggregateValue += nadaVal;
valueCount++;
}
}
aggregateAvgValue = aggregateValue / valueCount;
}
return aggregateAvgValue;
}
private decimal GetNadaValueByParam(string floorPricingNadaFirstValue,
CollateralValuation fpcParamsCollateralValuation)
{
if (floorPricingNadaFirstValue.ToLower().StartsWith("low"))
{
return fpcParamsCollateralValuation.LowAdjustedAuctionValue;
}
if (floorPricingNadaFirstValue.ToLower().StartsWith("avg"))
{
return fpcParamsCollateralValuation.AverageAdjustedAuctionValue;
}
return fpcParamsCollateralValuation.HighAdjustedAuctionValue;
}
private decimal GetMMRValueByParam(string floorPricingMmrValue, ImsAutoFileData fpcParamsImsAutoFileData,
decimal defaultValue)
{
if (floorPricingMmrValue.ToLower().StartsWith("regional"))
{
return fpcParamsImsAutoFileData.MMRRegionalAdjustedPrice ?? defaultValue;
}
return fpcParamsImsAutoFileData.MMRNationalAdjustedPrice ?? defaultValue;
}
public int GetStrategyWeight()
{
return 99; // This is the preferred strategy.
}
}
}
@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using LCU.API.Collections.CollectionsApiLib.Models;
using LCU.Shared.Collections.RepoApiLib.Models;
namespace LCU.API.Collections.RepoApi.Services
{
public interface IRedemptionExpiredDateCalculator
{
DateTime? CalculateAsync(TemenosCase temenosCase,
IReadOnlyCollection<RepoLetter> readOnlyCollection);
}
}
@@ -0,0 +1,26 @@
using System.Collections.Generic;
namespace LCU.API.Collections.RepoApi.Domain.Processing
{
public class AlertProcessingSlip
{
public RepoAccountProcessingSlip AccountProcessingSlip { get; set; }
public string Message { get; set; }
public List<string> WatchedStatusTypes { get; set; }
public List<string> WatchedTransactionTypes { get; set; }
public AlertProcessingSlip
(
RepoAccountProcessingSlip accountProcessingSlip,
string message,
List<string> watchedStatusTypes,
List<string> watchedTransactionTypes
)
{
AccountProcessingSlip = accountProcessingSlip;
Message = message;
WatchedStatusTypes = watchedStatusTypes;
WatchedTransactionTypes = watchedTransactionTypes;
}
}
}
@@ -0,0 +1,96 @@
using System.Collections.Generic;
using LCU.API.Collections.CollectionsApiLib.Models;
using LCU.API.Collections.RepoApi.Exceptions;
using LCU.Shared.Collections.RepoApiLib.Models;
using LCU.Shared.Collections.RepoApiLib.Models.Consumer;
using LCU.Shared.Collections.RepoApiLib.Models.Producer;
using LCU.Shared.Collections.RepoApiLib.Models.Settings;
namespace LCU.API.Collections.RepoApi.Domain.Processing
{
/**
* This class represents everything you need to do a logical update or creation of a RepoAccount
*/
public class RepoAccountProcessingSlip
{
public ProcessingItem RequestedBy { get; set; }
public string AccountNo { get; set; }
public int CaseId { get; set; }
public TemenosAccount RecoveryAccount { get; set; }
public TemenosCase RecoveryCase { get; set; }
public RepoAccount ExistingRepoAccount { get; set; }
public RepoAccount UpdatedRepoAccount { get; set; }
public Settings CurrentSettings { get; set; }
public IReadOnlyCollection<CoreTransaction> TransactionHistory { get; set; }
public MemberDetail Member { get; set; }
public LoanDetail Loan { get; set; }
public IReadOnlyCollection<AuctionAssignment> AuctionAssignments { get; set; }
public IReadOnlyCollection<RepoLetter> Letters { get; set; }
public void AssertReadyForPreChecks()
{
if (RequestedBy == null)
{
throw new MissingRequirementException("Processing Item",
$"RepoAccount Processing Slip requires a processing item.");
}
if (RecoveryAccount == null)
{
throw new MissingRequirementException("Recovery Account",
$"{RequestedBy.AccountNo} did not have account.");
}
if (CurrentSettings == null)
{
throw new MissingRequirementException("Settings",
$"RepoAccount Processing Slip requires settings.");
}
}
public void AssertReadyForFullProcessing()
{
AssertReadyForPreChecks();
if (RecoveryCase == null)
{
throw new MissingRequirementException("Recovery Case",
$"{RecoveryAccount.AccountId} did not have case {CaseId}.");
}
if (Loan == null)
{
throw new MissingRequirementException("Loan", "Loan required to process.");
}
if (Letters == null)
{
throw new MissingRequirementException("Letters", "Letters required to process.");
}
}
public void AssertReadyForAlertChecks()
{
AssertReadyForFullProcessing();
if (TransactionHistory == null && ExistingRepoAccount.ReferenceStatus != "Chargeoff")
{
throw new MissingRequirementException("Transactions", "Transactions required to process alerts.");
}
if (Member == null)
{
throw new MissingRequirementException("Member", "Member required to process alerts.");
}
if (AuctionAssignments == null)
{
throw new MissingRequirementException("Auction Assignments", "Auction Assignments required to process alerts.");
}
}
}
}
@@ -0,0 +1,41 @@
using System;
using System.Collections.Generic;
using System.Linq;
using LCU.API.Collections.CollectionsApiLib.Models;
using LCU.API.Collections.RepoApi.Services;
using LCU.Shared.Collections.RepoApiLib.Models;
using Serilog;
namespace LCU.API.Collections.RepoApi.Domain
{
public class RedemptionExpiredDateCalculator : IRedemptionExpiredDateCalculator
{
public DateTime? CalculateAsync(TemenosCase temenosCase, IReadOnlyCollection<RepoLetter> letters)
{
try
{
// !!!!! 04/15/2021, Bill and Kyle don't want to check for letters, they just want to use the date.
// var redemptionLettersWi = letters.Where(x => x.ActionCode == "LTR35" || x.ActionCode == "LTR34" ).ToList();
// var redemptionLettersIl = letters.Where(x => x.ActionCode == "703" || x.ActionCode == "701").ToList();
// if (redemptionLettersIl.Any() && redemptionLettersWi.Any())
// {
// return null;
// }
if (temenosCase.RedemptionExpiredDateWi != null) { // redemptionLettersWi.Any())}
return temenosCase.RedemptionExpiredDateWi;
}
if( temenosCase.RedemptionExpiredDateIl != null) { // redemptionLettersIl.Any())}
return temenosCase.RedemptionExpiredDateIl;
}
}
catch (Exception ex)
{
Log.Error(ex, $"Failed To Get Redemption Exp. Date. TemenosCaseID: {temenosCase.CaseId}", temenosCase);
}
return null;
}
}
}