Files
monorepo/LCU.API.Operations.IncentiveAPI/LCU.API.Internal.Operations.IncentiveApi/Services/AutoDecision/ActivityDecisionService.cs
T
2026-04-23 16:27:51 +00:00

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;
}
}