412 lines
16 KiB
C#
412 lines
16 KiB
C#
using LCU.API.Internal.Operations.IncentiveApi.Exceptions;
|
|
using LCU.API.Internal.Operations.IncentiveApi.Extensions;
|
|
using LCU.Library.Internal.IncentiveApiModels.Entities;
|
|
using LCU.Library.Internal.IncentiveApiModels.Enums;
|
|
using LCU.Library.Internal.IncentiveApiModels.Common;
|
|
using LCU.Library.Internal.IncentiveApiModels.Requests;
|
|
using LCU.Library.Internal.IncentiveApiModels.Results;
|
|
using LCU.Library.Internal.ApiResponse.Services;
|
|
|
|
namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision;
|
|
|
|
public class ActivityDecisionService : IActivityDecisionService
|
|
{
|
|
private const int PageItemLimit = 1000;
|
|
private readonly IEnumerable<IDecisionStrategy> _strategies;
|
|
private readonly IActivityService _activityService;
|
|
private readonly IActivityDetailService _activityDetailService;
|
|
private readonly DebtProtectionAccessoryService _debtProtectionAccessoryService;
|
|
private readonly IDnaService _dnaService;
|
|
private static readonly List<string> TransactionCodes = new List<string> { "XDEP" };
|
|
private readonly ProcessResult _incentiveProcessResult;
|
|
|
|
public ActivityDecisionService(
|
|
IEnumerable<IDecisionStrategy> strategies,
|
|
IActivityService activityService,
|
|
IActivityDetailService activityDetailService,
|
|
DebtProtectionAccessoryService debtProtectionAccessoryService,
|
|
IDnaService dnaService
|
|
)
|
|
{
|
|
_strategies = strategies;
|
|
_activityService = activityService;
|
|
_activityDetailService = activityDetailService;
|
|
_debtProtectionAccessoryService = debtProtectionAccessoryService;
|
|
_dnaService = dnaService;
|
|
_incentiveProcessResult = new ProcessResult();
|
|
}
|
|
|
|
public async Task RunNightlyDecisionProcess(NightlyIncentiveProcessRequest request)
|
|
{
|
|
try
|
|
{
|
|
var decisionedCreated = await DecisionActivityByState(ActivityState.Created);
|
|
var createdStateResults = $"Auto-decisioned {decisionedCreated.Count} activities in Created state.";
|
|
Log.Information(createdStateResults);
|
|
_incentiveProcessResult.MessageStack.Add(createdStateResults);
|
|
|
|
var decisionedManualReview = await DecisionActivityByState(ActivityState.ManualReview);
|
|
var manualStateActivityResults = $"Auto-decisioned {decisionedManualReview.Count} activities in Manual Review state.";
|
|
Log.Information(manualStateActivityResults);
|
|
_incentiveProcessResult.MessageStack.Add(manualStateActivityResults);
|
|
|
|
var decisionManualReviewUpdated = await UpdateAndDecideManualReviewActivities();
|
|
var manualReviewUpdates = $"Updated and Auto-Decisioned {decisionManualReviewUpdated.Count} Activities.";
|
|
Log.Information(manualReviewUpdates);
|
|
_incentiveProcessResult.MessageStack.Add(manualReviewUpdates);
|
|
|
|
var decisionUpdateDpEligible = await UpdateAndDecideDpEligibleLoanActivities();
|
|
var dpEligibleDecisionResults = $"Updated and Auto-Decisioned {decisionUpdateDpEligible.Count} Debt Protection Eligible Loan Activities.";
|
|
Log.Information(dpEligibleDecisionResults);
|
|
_incentiveProcessResult.MessageStack.Add(dpEligibleDecisionResults);
|
|
|
|
await QueueReturnResponse.QueueResponse(request, _incentiveProcessResult.Success, _incentiveProcessResult.MessageStack);
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
var message = $"There was an issue with handling the Daily Decision Process Exception Message: {exception.Message}.";
|
|
_incentiveProcessResult.MessageStack.Add(message);
|
|
_incentiveProcessResult.Success = false;
|
|
await QueueReturnResponse.QueueResponse(request, _incentiveProcessResult.Success, _incentiveProcessResult.MessageStack);
|
|
}
|
|
}
|
|
|
|
public async Task<ActivityStateChange> TryDecisionActivity(Activity activity)
|
|
{
|
|
var strategy = _strategies.FirstOrDefault(x => x.CanDecideActivity(activity));
|
|
|
|
if (strategy != null)
|
|
{
|
|
return await strategy.DecideActivity(activity);
|
|
}
|
|
|
|
throw new NoAvailableDecisionStrategy(
|
|
$"No decision strategy available, {activity.Id} - {activity.Product}:{activity.Type}");
|
|
}
|
|
|
|
public async Task<IReadOnlyCollection<ActivityStateChange>> DecisionActivityByState(ActivityState state)
|
|
{
|
|
var changes = new List<ActivityStateChange>();
|
|
var allItemsProcessed = false;
|
|
var page = 0;
|
|
var processedItemCount = 0;
|
|
|
|
while (!allItemsProcessed)
|
|
{
|
|
try
|
|
{
|
|
var activityPage = await _activityService.ListByFilterAsync(new ActivityFilter
|
|
{
|
|
Limit = PageItemLimit,
|
|
Page = page += 1,
|
|
State = new List<ActivityState> { state }
|
|
});
|
|
|
|
if (activityPage?.Items == null)
|
|
{
|
|
return new List<ActivityStateChange>();
|
|
}
|
|
|
|
var activities = activityPage.Items.ToList();
|
|
changes.AddRange(await AutoDecideActivities(activities));
|
|
processedItemCount += activities.Count;
|
|
allItemsProcessed = processedItemCount == activityPage.Total;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, $"Failed to process all updates for activities in State:{state}");
|
|
allItemsProcessed = true;
|
|
}
|
|
}
|
|
|
|
return changes;
|
|
}
|
|
|
|
public async Task<IReadOnlyCollection<ActivityStateChange>> UpdateAndDecideManualReviewActivities()
|
|
{
|
|
var changes = new List<ActivityStateChange>();
|
|
var allItemsProcessed = false;
|
|
var page = 0;
|
|
var processedItemCount = 0;
|
|
|
|
while (!allItemsProcessed)
|
|
{
|
|
try
|
|
{
|
|
var activityPage = await _activityService.ListByFilterAsync(new ActivityFilter
|
|
{
|
|
Limit = PageItemLimit,
|
|
Page = page += 1,
|
|
State = new List<ActivityState> { ActivityState.ManualReview }
|
|
});
|
|
|
|
if (activityPage?.Items == null)
|
|
{
|
|
return new List<ActivityStateChange>();
|
|
}
|
|
|
|
var activities = activityPage.Items.ToList();
|
|
var updatedActivities = await _activityDetailService.TryUpdateActivityAccountDetails(activities);
|
|
|
|
changes.AddRange(await AutoDecideActivities(updatedActivities));
|
|
processedItemCount += activities.Count;
|
|
allItemsProcessed = processedItemCount == activityPage.Total;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, $"Failed to process all Manual Review updates");
|
|
allItemsProcessed = true;
|
|
}
|
|
}
|
|
|
|
return changes;
|
|
}
|
|
|
|
public async Task<IReadOnlyCollection<ActivityStateChange>> UpdateAndDecideDpEligibleLoanActivities()
|
|
{
|
|
var dateLow = DateTime.Now.StartOfMonth().AddMonths(-2).AddDays(-1);
|
|
var dpEligibleProducts = new List<Product> { Product.DebtProtectionEligibleLoan, Product.Mortgage, Product.Vehicle };
|
|
var changes = new List<ActivityStateChange>();
|
|
var allItemsProcessed = false;
|
|
var page = 0;
|
|
var processedItemCount = 0;
|
|
|
|
while (!allItemsProcessed)
|
|
{
|
|
try
|
|
{
|
|
var activityResults = new PagedResult<Activity>();
|
|
activityResults = await _activityService.ListByFilterAsync(new ActivityFilter
|
|
{
|
|
Limit = PageItemLimit,
|
|
Page = page += 1,
|
|
DateLow = dateLow,
|
|
State = new List<ActivityState> { ActivityState.Accepted },
|
|
Product = dpEligibleProducts
|
|
});
|
|
|
|
if (activityResults.Items != null)
|
|
{
|
|
var activities = activityResults.Items.ToList();
|
|
var updatedDpActivities = await _debtProtectionAccessoryService.ProcessDpAccessoryUpdates(activities);
|
|
var updatedActivities = await _activityDetailService.TryUpdateDpEligibleLoanDetail(activities);
|
|
updatedActivities.AddRange(await _debtProtectionAccessoryService.ProcessDpAccessoryUpdates(updatedActivities));
|
|
updatedActivities.AddRange(updatedDpActivities);
|
|
changes.AddRange(await AutoDecideActivities(updatedActivities));
|
|
processedItemCount += activities.Count;
|
|
}
|
|
|
|
allItemsProcessed = processedItemCount == activityResults.Total;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, $"Failed to process all Debt Protection updates UpdateAndDecideDpEligibleLoanActivities");
|
|
allItemsProcessed = true;
|
|
}
|
|
}
|
|
|
|
changes.AddRange(await RejectDpOnRejectedDpEligibleLoanActivities(dateLow, dpEligibleProducts));
|
|
|
|
return changes;
|
|
}
|
|
|
|
private async Task<IReadOnlyCollection<ActivityStateChange>> RejectDpOnRejectedDpEligibleLoanActivities(DateTime dateLow, List<Product> dpEligibleProducts)
|
|
{
|
|
var changes = new List<ActivityStateChange>();
|
|
var allItemsProcessed = false;
|
|
var page = 0;
|
|
var processedItemCount = 0;
|
|
|
|
while (!allItemsProcessed)
|
|
{
|
|
try
|
|
{
|
|
var activityPage = await _activityService.ListByFilterAsync(new ActivityFilter
|
|
{
|
|
Limit = PageItemLimit,
|
|
Page = page += 1,
|
|
DateLow = dateLow,
|
|
State = new List<ActivityState> { ActivityState.Rejected },
|
|
Product = dpEligibleProducts
|
|
});
|
|
|
|
if (activityPage?.Items == null)
|
|
{
|
|
return new List<ActivityStateChange>();
|
|
}
|
|
var activities = activityPage.Items.ToList();
|
|
var dpActivitiesForRejection = await _debtProtectionAccessoryService.GetNonRejectedDpAccessoriesWithRejectedMatchedSale(activities);
|
|
|
|
changes.AddRange(await RejectDpActivities(dpActivitiesForRejection));
|
|
|
|
processedItemCount += activities.Count;
|
|
allItemsProcessed = processedItemCount == activityPage.Total;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, $"Failed to process all Debt Protection updates on RejectDpOnRejectedDpEligibleLoanActivities");
|
|
allItemsProcessed = true;
|
|
}
|
|
}
|
|
|
|
return changes;
|
|
}
|
|
|
|
public async Task<IReadOnlyCollection<ActivityStateChange>> UpdateAndDecideLicReferralActivities()
|
|
{
|
|
var changes = new List<ActivityStateChange>();
|
|
var allItemsProcessed = false;
|
|
var page = 0;
|
|
var processedItemCount = 0;
|
|
|
|
while (!allItemsProcessed)
|
|
{
|
|
try
|
|
{
|
|
var activityPage = await _activityService.ListByFilterAsync(new ActivityFilter
|
|
{
|
|
Limit = PageItemLimit,
|
|
Page = page += 1,
|
|
State = new List<ActivityState> { ActivityState.ManualReview },
|
|
Product = new List<Product> { Product.LIC }
|
|
});
|
|
|
|
if (activityPage?.Items == null)
|
|
{
|
|
return new List<ActivityStateChange>();
|
|
}
|
|
|
|
var activities = activityPage.Items.ToList();
|
|
var updatedActivities = await _activityDetailService.TryUpdateLicReferralStationDetail(activities);
|
|
|
|
changes.AddRange(await AutoDecideActivities(updatedActivities));
|
|
processedItemCount += activities.Count;
|
|
allItemsProcessed = processedItemCount == activityPage.Total;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, $"Failed to process all LIC Referral updates");
|
|
allItemsProcessed = true;
|
|
}
|
|
}
|
|
|
|
return changes;
|
|
}
|
|
|
|
public async Task<List<ActivityStateChange>> AutoDecideActivities(IEnumerable<Activity> activities)
|
|
{
|
|
var changes = new List<ActivityStateChange>();
|
|
foreach (var activity in activities.Where(x => x.CanAutoDecision()))
|
|
{
|
|
try
|
|
{
|
|
var decision = await TryDecisionActivity(activity);
|
|
if (decision.State == activity.State) continue;
|
|
await _activityService.ApplyStateChange(decision);
|
|
changes.Add(decision);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, $"Failed to decision activity {activity.Id}");
|
|
}
|
|
}
|
|
|
|
return changes;
|
|
}
|
|
|
|
private async Task<List<ActivityStateChange>> RejectDpActivities(IEnumerable<Activity> activities)
|
|
{
|
|
var changes = new List<ActivityStateChange>();
|
|
foreach (var activity in activities)
|
|
{
|
|
try
|
|
{
|
|
var change = new ActivityStateChange
|
|
{
|
|
ActivityId = activity.Id,
|
|
ActivityMatchId = activity.MatchingDpAccessoryActivityId,
|
|
Note = $"Debt Protection Matched Activity ID: {activity.MatchingDpAccessoryActivityId} Rejected",
|
|
State = ActivityState.Rejected
|
|
};
|
|
await _activityService.ApplyStateChange(change);
|
|
changes.Add(change);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, $"Failed to decision activity {activity.Id}");
|
|
}
|
|
}
|
|
|
|
return changes;
|
|
}
|
|
|
|
public async Task<IReadOnlyCollection<ActivityStateChange>> UpdateAndDecideDirectDepositPendingReferralActivities()
|
|
{
|
|
var activityPage = await _activityService.ListByFilterAsync(new ActivityFilter
|
|
{
|
|
Limit = PageItemLimit,
|
|
Page = 1,
|
|
State = new List<ActivityState> { ActivityState.PendingReferral },
|
|
Product = new List<Product> { Product.DirectDeposit },
|
|
Type = new List<ActivityType> { ActivityType.Referral }
|
|
});
|
|
|
|
if (activityPage?.Items == null)
|
|
{
|
|
return new List<ActivityStateChange>();
|
|
}
|
|
|
|
if (activityPage.Total > PageItemLimit)
|
|
{
|
|
Log.Warning($"More than {PageItemLimit} items to process in the Update And Decide Direct Deposit Pending Referrals!");
|
|
}
|
|
|
|
var activities = activityPage.Items.ToList();
|
|
var currentDateTime = DateTime.Now;
|
|
var changes = new List<ActivityStateChange>();
|
|
|
|
foreach (var activity in activities)
|
|
{
|
|
try
|
|
{
|
|
var activityHistory =
|
|
await _dnaService.GetTransactionHistory(activity.Detail.AccountNumber, activity.Created, currentDateTime, TransactionCodes);
|
|
|
|
if (activityHistory == null)
|
|
{
|
|
var decision = new ActivityStateChange
|
|
{
|
|
ActivityId = activity.Id,
|
|
State = ActivityState.Rejected,
|
|
Note = "No Account Was Found With The Given Account Number",
|
|
AcceptedDate = activity.Accepted.Date.AddHours(12)
|
|
};
|
|
|
|
await _activityService.ApplyStateChange(decision);
|
|
changes.Add(decision);
|
|
}
|
|
|
|
if (activityHistory != null && activityHistory.Any())
|
|
{
|
|
var decision = new ActivityStateChange
|
|
{
|
|
ActivityId = activity.Id,
|
|
State = ActivityState.Accepted,
|
|
Note = "Account Had At Least One External Deposit Transaction",
|
|
AcceptedDate = activity.Accepted.Date.AddHours(12)
|
|
};
|
|
|
|
await _activityService.ApplyStateChange(decision);
|
|
changes.Add(decision);
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Error(ex, $"Failed to save direct deposit referral state as accepted for activity:{activity.Id}");
|
|
}
|
|
}
|
|
|
|
return changes;
|
|
}
|
|
}
|