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 _strategies; private readonly IActivityService _activityService; private readonly IActivityDetailService _activityDetailService; private readonly DebtProtectionAccessoryService _debtProtectionAccessoryService; private readonly IDnaService _dnaService; private static readonly List TransactionCodes = new List { "XDEP" }; private readonly ProcessResult _incentiveProcessResult; public ActivityDecisionService( IEnumerable 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 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> DecisionActivityByState(ActivityState state) { var changes = new List(); 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 { state } }); if (activityPage?.Items == null) { return new List(); } 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> UpdateAndDecideManualReviewActivities() { var changes = new List(); 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.ManualReview } }); if (activityPage?.Items == null) { return new List(); } 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> UpdateAndDecideDpEligibleLoanActivities() { var dateLow = DateTime.Now.StartOfMonth().AddMonths(-2).AddDays(-1); var dpEligibleProducts = new List { Product.DebtProtectionEligibleLoan, Product.Mortgage, Product.Vehicle }; var changes = new List(); var allItemsProcessed = false; var page = 0; var processedItemCount = 0; while (!allItemsProcessed) { try { var activityResults = new PagedResult(); activityResults = await _activityService.ListByFilterAsync(new ActivityFilter { Limit = PageItemLimit, Page = page += 1, DateLow = dateLow, State = new List { 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> RejectDpOnRejectedDpEligibleLoanActivities(DateTime dateLow, List dpEligibleProducts) { var changes = new List(); 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.Rejected }, Product = dpEligibleProducts }); if (activityPage?.Items == null) { return new List(); } 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> UpdateAndDecideLicReferralActivities() { var changes = new List(); 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.ManualReview }, Product = new List { Product.LIC } }); if (activityPage?.Items == null) { return new List(); } 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> AutoDecideActivities(IEnumerable activities) { var changes = new List(); 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> RejectDpActivities(IEnumerable activities) { var changes = new List(); 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> UpdateAndDecideDirectDepositPendingReferralActivities() { var activityPage = await _activityService.ListByFilterAsync(new ActivityFilter { Limit = PageItemLimit, Page = 1, State = new List { ActivityState.PendingReferral }, Product = new List { Product.DirectDeposit }, Type = new List { ActivityType.Referral } }); if (activityPage?.Items == null) { return new List(); } 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(); 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; } }