This commit is contained in:
Zeeshaun
2026-03-17 09:53:09 -05:00
parent cdcba54aa4
commit 1a0702a79a
590 changed files with 62058 additions and 0 deletions
@@ -0,0 +1,160 @@
using System.Globalization;
using System.IO;
using CsvHelper;
using CsvHelper.Configuration;
using LCU.API.Internal.Operations.IncentiveApi.Models.Action;
using LCU.API.Internal.Operations.IncentiveApiLib.Repositories;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices;
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.API.Internal.Operations.IncentiveApi.Services.Transformations;
using LCU.Library.Internal.ApiResponse.Services;
using LCU.Library.Internal.Exceptions;
using LCU.Library.Internal.IncentiveApiModels.Results;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.Action;
public class ActionCreditCardImportService : IActionCreditCardImportService
{
private readonly IUserRepository _userRepository;
private readonly IActivityService _activityService;
private readonly IDeadActivityService _deadActivityService;
private readonly IFileOperationsApiService _fileOperationsApiService;
private readonly ProcessResult _actionCreditCardResult;
public ActionCreditCardImportService(IUserRepository userRepository, IFileOperationsApiService fileOperationsApiService,
IActivityService activityService, IDeadActivityService deadActivityService)
{
_userRepository = userRepository;
_fileOperationsApiService = fileOperationsApiService;
_activityService = activityService;
_deadActivityService = deadActivityService;
_actionCreditCardResult = new ProcessResult();
}
public async Task RunActionCreditCardProcess(ActionCreditCardRequest request)
{
try
{
var importedPersonal = await ImportTransactions<ActionCreditCardPersonalRow>("personal", request);
Log.Information($"Imported {importedPersonal.Count} credit cards - personal.");
var importedBusiness = await ImportTransactions<ActionCreditCardBusinessRow>("business", request);
Log.Information($"Imported {importedBusiness.Count} credit cards - business.");
_actionCreditCardResult.MessageStack.Add($"Imported {importedPersonal.Count()} personal and {importedBusiness.Count()} business credit cards.");
await QueueReturnResponse.QueueResponse(request, _actionCreditCardResult.Success, _actionCreditCardResult.MessageStack);
}
catch (Exception exception)
{
var message = $"There was an issue with handling Import Action Credit Card Sales Exception Message: {exception.Message}.";
_actionCreditCardResult.MessageStack.Add(message);
_actionCreditCardResult.Success = false;
await QueueReturnResponse.QueueResponse(request, _actionCreditCardResult.Success, _actionCreditCardResult.MessageStack);
throw new ServiceException(message);
}
}
public async Task<IReadOnlyCollection<Activity>> ImportTransactions<T>(string fileIdentifier, ActionCreditCardRequest request) where T : IActionFileObject
{
var dynamicFileListRequest = request.ToDynamicActionCreditCardFileListRequest();
var files = await _fileOperationsApiService.BuildDynamicFileList(dynamicFileListRequest);
var activity = new List<Activity>();
foreach (var fileName in files)
{
var stringContent = _fileOperationsApiService.GetFileContent($"{request.FinalBasePath}{fileName}");
using var stringReader = new StringReader(stringContent.Result);
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
Delimiter = ",",
HasHeaderRecord = true,
HeaderValidated = null,
MissingFieldFound = null,
IgnoreBlankLines = true
};
using var csv = new CsvReader(stringReader, config);
var records = csv.GetRecords<T>().ToList();
var allActivity = await CreateActivity(records);
await _activityService.SaveActivityRangeAsync(allActivity);
activity.AddRange(allActivity);
}
return activity;
}
public async Task<IReadOnlyCollection<Activity>> CreateActivity<T>(IEnumerable<T> transactions) where T : IActionFileObject
{
var activities = new List<Activity>();
var deadActivities = new List<DeadActivity>();
foreach (var actionSaleRow in transactions)
{
var email = actionSaleRow.GetEmployeeEmail();
var employee = string.IsNullOrEmpty(email) ? null : await FindEmployee(email);
if (employee == null)
{
email = actionSaleRow.GetLoanOfficerEmail();
employee = string.IsNullOrEmpty(email) ? null : await FindEmployee(email);
}
if (employee == null)
{
email = actionSaleRow.GetLoanOfficerEmailByName();
employee = string.IsNullOrEmpty(email) ? null : await FindEmployee(email);
}
if(employee == null){
Log.Warning($"Action Import, No user for {email}. Saving as Dead Activity.");
deadActivities.Add(actionSaleRow.CreateDeadActivity(DeadActivityReason.MissingRequiredField, $"Action Import, No user for {email}", email));
continue;
}
var homeDept = employee.FindHomeDepartment();
if (homeDept == null)
{
Log.Warning($"User has no reliably determinable home department, {employee.EmployeeId}. Saving as Dead Activity.");
deadActivities.Add(
actionSaleRow.CreateDeadActivity(DeadActivityReason.MissingRequiredField,
$"User has no reliably determinable home department, {employee.EmployeeId}", email, employee.EmployeeId));
continue;
}
var activity = actionSaleRow.CreateActivity(employee);
if (activities.Any(x => x.SourceId == activity.SourceId))
{
Log.Fatal(
$"Duplicate activity in credit card file for Incentive Manager, sourceId is {activity.SourceId}");
continue;
}
activities.Add(activity);
}
try
{
await _deadActivityService.SaveDeadActivityRange(deadActivities);
}
catch (Exception ex)
{
Log.Error(ex, "Failed to save dead activities from ActionCreditCardImportService. Dead Activities will be lost.");
}
return activities;
}
private async Task<AppUser> FindEmployee(string employeeEmail)
{
try
{
return await _userRepository.FindUserByAdEmailAsync(employeeEmail);
}
catch(Exception ex)
{
Log.Warning(ex, "Error when finding user by email in ActionCreditCardImportService.");
return null;
}
}
}
@@ -0,0 +1,172 @@
using System.Globalization;
using System.IO;
using CsvHelper;
using CsvHelper.Configuration;
using LCU.API.Internal.Operations.IncentiveApi.Models.Action;
using LCU.API.Internal.Operations.IncentiveApiLib.Repositories;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices;
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.API.Internal.Operations.IncentiveApi.Services.Transformations;
using LCU.Library.Internal.IncentiveApiModels.Results;
using LCU.Library.Internal.ApiResponse.Services;
using LCU.Library.Internal.Exceptions;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.Action;
public class ActionVehicleImportService : IActionVehicleImportService
{
private readonly IUserRepository _userRepository;
private readonly IActivityService _activityService;
private readonly IDeadActivityService _deadActivityService;
private readonly IFileOperationsApiService _fileOperationsApiService;
private readonly ProcessResult _actionVehicleProcessResult;
static readonly CultureInfo USEnglish = new CultureInfo("en-US");
public ActionVehicleImportService(IUserRepository userRepository, IFileOperationsApiService fileOperationsApiService, IActivityService activityService, IDeadActivityService deadActivityService)
{
_userRepository = userRepository;
_fileOperationsApiService = fileOperationsApiService;
_activityService = activityService;
_deadActivityService = deadActivityService;
_actionVehicleProcessResult = new ProcessResult();
}
public async Task RunActionVehicleProcess(ActionVehicleRequest request)
{
try
{
var importedVehicleSales = await ImportTransactions<ActionSaleRow>(request);
Log.Information($"Imported {importedVehicleSales.Count} Action Vehicle Sales.");
_actionVehicleProcessResult.MessageStack.Add($"Imported {importedVehicleSales.Count()} Action Vehicle Sales.");
await QueueReturnResponse.QueueResponse(request, _actionVehicleProcessResult.Success, _actionVehicleProcessResult.MessageStack);
}
catch (Exception exception)
{
var message = $"There was an issue with handling Import Action Vehicle Sales Exception Message: {exception.Message}.";
_actionVehicleProcessResult.MessageStack.Add(message);
_actionVehicleProcessResult.Success = false;
await QueueReturnResponse.QueueResponse(request, _actionVehicleProcessResult.Success, _actionVehicleProcessResult.MessageStack);
throw new ServiceException(message);
}
}
public async Task<IReadOnlyCollection<Activity>> ImportTransactions<T>(ActionVehicleRequest request) where T : ActionSaleRow
{
var dynamicFileListRequest = request.ToDynamicActionVehicleFileListRequest();
var files = await _fileOperationsApiService.BuildDynamicFileList(dynamicFileListRequest);
var activity = new List<Activity>();
foreach (var fileName in files)
{
var stringContent = await _fileOperationsApiService.GetFileContent($"{request.FinalBasePath}{fileName}");
using var stringReader = new StringReader(stringContent);
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
Delimiter = ",",
HasHeaderRecord = true,
HeaderValidated = null,
MissingFieldFound = null,
IgnoreBlankLines = true
};
using var csv = new CsvReader(stringReader, config);
var records = csv.GetRecords<T>().ToList();
var allActivity = await CreateActivity(records);
await _activityService.SaveActivityRangeAsync(allActivity);
activity.AddRange(allActivity);
}
return activity;
}
public async Task<IReadOnlyCollection<Activity>> CreateActivity(IEnumerable<ActionSaleRow> transactions)
{
var activities = new List<Activity>();
var deadActivities = new List<DeadActivity>();
foreach (var actionSaleRow in transactions)
{
var activity = new Activity()
{
Product = Product.Vehicle,
Type = ActivityType.Sale,
State = ActivityState.Accepted,
SourceSystem = SystemType.Action,
SourceCreated = DateTime.Parse(actionSaleRow.OPEN_DATE),
Accepted = DateTime.Parse(actionSaleRow.OPEN_DATE).Date.AddHours(12),
SourceId = actionSaleRow.IndAppID,
Detail = new ActivityDetail()
{
AccountNumber = actionSaleRow.Account_Number,
MemberName = actionSaleRow.Borrower_Name,
AccountBalance = ParseMoneyDouble(actionSaleRow.IndAmtFinanced)
},
EmployeeEmail = null,
EmployeeId = null,
DepartmentId = -1
};
var employee = await FindEmployee(actionSaleRow.EmailAddress);
if (employee == null)
{
Log.Warning($"Action Import, No user for {actionSaleRow.EmailAddress}. Saving as Dead Activity.");
activity.EmployeeEmail = actionSaleRow.EmailAddress;
deadActivities.Add(new DeadActivity(activity,
DeadActivityReason.MissingRequiredField,
$"Action Import, No user for {actionSaleRow.EmailAddress}")
);
continue;
}
activity.AppUserId = employee.Id;
activity.EmployeeEmail = employee.AdEmail;
activity.EmployeeId = employee.EmployeeId;
var homeDept = employee.FindHomeDepartment();
if (homeDept == null)
{
Log.Warning($"User has no reliably determinable home department, {employee.EmployeeId}. Saving as Dead Activity.");
deadActivities.Add(new DeadActivity(activity,
DeadActivityReason.MissingRequiredField,
$"User has no reliably determinable home department, {employee.EmployeeId}. Saving as Dead Activity."));
continue;
}
activity.DepartmentId = homeDept.DepartmentId;
activities.Add(activity);
}
try
{
await _deadActivityService.SaveDeadActivityRange(deadActivities);
}
catch (Exception ex)
{
Log.Error(ex, "Failed to save dead activities from ActionVehicleImportService. Dead Activities will be lost.");
}
return activities;
}
public double? ParseMoneyDouble(string indAmtFinanced)
{
var parsed = decimal.Parse(indAmtFinanced, System.Globalization.NumberStyles.Currency, USEnglish);
return (double)parsed;
}
private async Task<AppUser> FindEmployee(string employeeEmail)
{
try
{
return await _userRepository.FindUserByAdEmailAsync(employeeEmail);
}
catch (Exception ex)
{
Log.Error(ex, "Error when finding user by email in ActionVehicleImportService.");
return null;
}
}
}
@@ -0,0 +1,12 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.API.Internal.Operations.IncentiveApi.Models.Action;
using LCU.Library.Internal.IncentiveApiModels.Requests;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.Action;
public interface IActionCreditCardImportService
{
Task RunActionCreditCardProcess(ActionCreditCardRequest request);
Task<IReadOnlyCollection<Activity>> ImportTransactions<T>(string fileIdentifier, ActionCreditCardRequest request) where T : IActionFileObject;
Task<IReadOnlyCollection<Activity>> CreateActivity<T>(IEnumerable<T> transactions) where T : IActionFileObject;
}
@@ -0,0 +1,12 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.API.Internal.Operations.IncentiveApi.Models.Action;
using LCU.Library.Internal.IncentiveApiModels.Requests;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.Action;
public interface IActionVehicleImportService
{
Task RunActionVehicleProcess(ActionVehicleRequest request);
Task<IReadOnlyCollection<Activity>> ImportTransactions<T>(ActionVehicleRequest request) where T : ActionSaleRow;
Task<IReadOnlyCollection<Activity>> CreateActivity(IEnumerable<ActionSaleRow> transactions);
}
@@ -0,0 +1,224 @@
using LCU.API.Internal.Operations.IncentiveApi.Models;
using LCU.API.Internal.Operations.IncentiveApiLib.Repositories;
using LCU.Library.Internal.IncentiveApiModels.Entities;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public class ActivityDetailService : IActivityDetailService
{
private readonly IActivityDetailUpdateRepository _activityDetailUpdateRepository;
private readonly IUnitOfWork _unitOfWork;
private readonly IDnaService _dnaService;
public ActivityDetailService(
IActivityDetailUpdateRepository activityDetailUpdateRepository,
IUnitOfWork unitOfWork,
IDnaService dnaService
)
{
_activityDetailUpdateRepository = activityDetailUpdateRepository;
_unitOfWork = unitOfWork;
_dnaService = dnaService;
}
public async Task<List<ActivityDetailUpdate>> GetAllActivityDetailUpdates(int activityDetailId)
{
return await _activityDetailUpdateRepository.GetActivityDetailUpdatesByActivityDetailId(activityDetailId);
}
public async Task<List<Activity>> TryUpdateActivityAccountDetails(List<Activity> activities)
{
var accountDetails = await _dnaService.GetNewAccountDetailsForActivities(activities);
var updates = new List<Activity>();
if (!activities.Any() || !accountDetails.Any())
{
return updates;
}
foreach (var activity in activities)
{
var updateDetails = accountDetails.Where(x => x.AccountNumber.ToString()
.Equals(activity.Detail.AccountNumber, StringComparison.InvariantCultureIgnoreCase))
.ToList();
if (!updateDetails.Any())
{
continue;
}
foreach (var detail in updateDetails.Where(detail =>
detail.CurrentAccountStatus != null &&
detail.CurrentAccountStatus != activity.Detail.AccountStatus))
{
await _activityDetailUpdateRepository.SaveActivityDetailUpdate(new ActivityDetailUpdate
{
ActivityDetailId = activity.Detail.Id,
Note =
$"The Account Status has changed from {activity.Detail.AccountStatus} to {detail.CurrentAccountStatus}"
});
activity.Detail.AccountStatus = detail.CurrentAccountStatus;
updates.Add(activity);
}
}
await _unitOfWork.CompleteAsync();
return updates;
}
public async Task<List<Activity>> TryUpdateLicReferralStationDetail(List<Activity> activities)
{
var activityManagerReferrals =
await _dnaService.QueryActivityManagerForActivities(activities.Select(x => x.SourceId).ToList());
var updates = new List<Activity>();
if (!activityManagerReferrals.Any() || !activities.Any())
{
return updates;
}
foreach (var activity in activities)
{
var updateDetails = activityManagerReferrals.Where(x =>
x.ActivityId.ToString().Equals(activity.SourceId, StringComparison.InvariantCultureIgnoreCase))
.ToList();
if (!updateDetails.Any())
{
continue;
}
foreach (var detail in updateDetails.Where(detail => detail.ReferralStation != null && activity.Detail.StationName != detail.ReferralStation))
{
await _activityDetailUpdateRepository.SaveActivityDetailUpdate(new ActivityDetailUpdate
{
ActivityDetailId = activity.Detail.Id,
Note =
$"The StationName has changed from {activity.Detail.StationName} to {detail.ReferralStation}"
});
activity.Detail.StationName = detail.ReferralStation;
if (detail.ReferralStation == "LIC Referral-Qualified" || detail.ReferralStation == "LIC Referral- Not Qualified") {
activity.Accepted = detail.StationDateTime;
}
updates.Add(activity);
}
}
await _unitOfWork.CompleteAsync();
return updates;
}
public async Task<Activity> TryUpdateDpAccessory(Activity saleActivity, Activity dpAccessoryActivity)
{
var updatedActivity = await MapAndExecuteActivityDetailUpdate(
dpAccessoryActivity,
saleActivity.Detail.DebtProtectionAddDate,
saleActivity.Detail.DebtProtectionRemoveDate
);
await _unitOfWork.CompleteAsync();
return updatedActivity;
}
public async Task<List<Activity>> TryUpdateDpEligibleLoanDetail(List<Activity> existingActivities)
{
var updateOrders = new List<ActivityDetailUpdateOrder>();
var updateActivities = await _dnaService.GetNewAccountDetailsForActivities(existingActivities);
var updatedActivities = new List<Activity>();
if (!existingActivities.Any() || !updateActivities.Any())
{
return updatedActivities;
}
foreach (var activity in existingActivities)
{
updateOrders.Add(
new ActivityDetailUpdateOrder
{
ExistingActivity = activity,
DetailUpdates = updateActivities.Where(x => x.AccountNumber.ToString()
.Equals(activity.Detail.AccountNumber, StringComparison.InvariantCultureIgnoreCase))
.ToList()
}
);
}
foreach (var updateOrder in updateOrders)
{
try
{
if (updateOrder.DetailUpdates.Count == 0)
{
continue;
}
var latestDpAdded = updateOrder.DetailUpdates.MaxBy(x => x.DebtProtectionEffectiveDate)?.DebtProtectionEffectiveDate;
var latestDpRemoved = updateOrder.DetailUpdates.OrderByDescending(x => x.DebtProtectionInactiveDate == null)
.ThenByDescending(x => x.DebtProtectionInactiveDate)
.FirstOrDefault()
?.DebtProtectionInactiveDate;
if (IsDebtProtectionUpdateNeeded(updateOrder.ExistingActivity, latestDpAdded, latestDpRemoved))
{
updatedActivities.Add(await MapAndExecuteActivityDetailUpdate(updateOrder.ExistingActivity, latestDpAdded, latestDpRemoved));
}
}
catch (Exception ex)
{
Log.Error(ex, $"Failed to update activity details for activity ID:{updateOrder.ExistingActivity.Id}");
}
}
await _unitOfWork.CompleteAsync();
return updatedActivities;
}
private static bool IsDebtProtectionUpdateNeeded(Activity activity, DateTime? latestDpAdded, DateTime? latestDpRemoved)
{
return activity.Detail.DebtProtectionAddDate?.Date != latestDpAdded?.Date
|| activity.Detail.DebtProtectionRemoveDate?.Date != latestDpRemoved?.Date;
}
private async Task<Activity> MapAndExecuteActivityDetailUpdate(Activity activity, DateTime? latestDpAdded, DateTime? latestDpRemoved)
{
try
{
if (activity.Detail.DebtProtectionAddDate?.Date != latestDpAdded?.Date)
{
activity.Detail.DebtProtectionAddDate = latestDpAdded?.Date.AddHours(12);
await _activityDetailUpdateRepository.SaveActivityDetailUpdate(new ActivityDetailUpdate
{
ActivityDetailId = activity.Detail.Id,
Note = $"DebtProtectionAddDate has changed from {activity.Detail.DebtProtectionAddDate?.Date} to {latestDpAdded?.Date}"
});
}
if (activity.Detail.DebtProtectionRemoveDate?.Date != latestDpRemoved?.Date)
{
activity.Detail.DebtProtectionRemoveDate = latestDpRemoved?.Date.AddHours(12);
await _activityDetailUpdateRepository.SaveActivityDetailUpdate(new ActivityDetailUpdate
{
ActivityDetailId = activity.Detail.Id,
Note = $"DebtProtectionRemoveDate has changed from {activity.Detail.DebtProtectionRemoveDate?.Date} to {latestDpRemoved?.Date}"
});
}
}
catch (Exception ex)
{
Log.Error(ex, $"Failed to update activity details for activity ID:{activity.Id}");
}
return activity;
}
public async Task<ActivityDetailUpdate> SaveActivityDetailUpdate(ActivityDetailUpdate activityDetailUpdate)
{
return await _activityDetailUpdateRepository.SaveActivityDetailUpdate(activityDetailUpdate);
}
}
@@ -0,0 +1,748 @@
using LCU.API.Internal.Operations.IncentiveApi.Exceptions;
using LCU.API.Internal.Operations.IncentiveApi.Services.DuplicateDetection;
using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer;
using LCU.API.Internal.Operations.IncentiveApiLib.Extensions;
using LCU.API.Internal.Operations.IncentiveApi.Models;
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
using LCU.API.Internal.Operations.IncentiveApiLib.Services;
using LCU.API.Internal.Operations.IncentiveApiLib.Repositories;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.Library.Internal.IncentiveApiModels.Common;
using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices;
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.Library.Internal.IncentiveApiModels.Results;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public class ActivityService : IActivityService
{
private const string TransferredFromString = "Activity has been transferred from Activity ID: {0}";
private const string TransferredToString = "Activity has been transferred to Activity ID: {0}";
private readonly IActivityRepository _activityRepository;
private readonly IActivityChangeRepository _activityChangeRepository;
private readonly IActivityTransferActionRepository _activityTransferActionRepository;
private readonly IUserService _userService;
private readonly INotificationApiService _notificationService;
private readonly IUnitOfWork _unitOfWork;
private readonly IDuplicateDetectionService _duplicateDetectionService;
private readonly IAuthorizationService _authorizationService;
private readonly ProcessResult _incentiveProcessResult;
public ActivityService(
IActivityRepository activityRepository,
IActivityChangeRepository activityChangeRepository,
IActivityTransferActionRepository activityTransferActionRepository,
IUserService userService,
INotificationApiService notificationService,
IDuplicateDetectionService duplicateDetectionService,
IAuthorizationService authorizationService,
IUnitOfWork unitOfWork
)
{
_activityRepository = activityRepository;
_activityChangeRepository = activityChangeRepository;
_activityTransferActionRepository = activityTransferActionRepository;
_userService = userService;
_notificationService = notificationService;
_unitOfWork = unitOfWork;
_authorizationService = authorizationService;
_duplicateDetectionService = duplicateDetectionService;
_incentiveProcessResult = new ProcessResult();
}
public async Task<int> SaveActivityAsync(Activity activity)
{
if (LogGapInformation(activity))
{
Log.Information($"Activity {activity.Id} - {activity.Detail.MemberName} " +
$"GapEligible saved as : {activity.Detail.IsGapInsuranceEligible} {DateTime.UtcNow} ");
}
if (string.IsNullOrWhiteSpace(activity.EmployeeId))
{
throw new DeadActivityException(
$"Attempted to save activity missing EmployeeId.");
}
if (activity.DepartmentId < 0)
{
throw new DeadActivityException(
$"Attempted to save activity with invalid DepartmentId of {activity.DepartmentId}");
}
var appUser = await _userService.GetActiveUserByEmployeeIdAsync(activity.EmployeeId)
?? await _userService.GetUserByEmployeeIdAsync(activity.EmployeeId);
if (appUser == null)
{
throw new NotFoundException($"Employee Not Found For EmployeeId: {activity.EmployeeId}");
}
activity.AppUserId = appUser.Id;
activity.Accepted = activity.Accepted.Date.AddHours(12);
await _activityRepository.SaveActivityAsync(activity);
await _unitOfWork.CompleteAsync();
await MarkDuplicates(new List<Activity> { activity });
return activity.Id;
}
public static bool LogGapInformation(Activity activity)
{
return activity.Product == Product.Vehicle
&& activity.SourceSystem == SystemType.Dna
&& activity.Detail != null
&& activity.Detail.IsGapInsuranceEligible == false
&& activity.Detail.AccountBalance.HasValue
&& activity.Detail.AccountBalance.Value <= 100000
&& (!string.IsNullOrEmpty(activity.Detail?.MemberName))
&& (!activity.Detail.MemberName.ToLower().Contains(" inc") && !activity.Detail.MemberName.Contains(" llc"));
}
public async Task<Activity> CreateAndRetrieveActivity(Activity activity)
{
var newActivityId = await SaveActivityAsync(activity);
return await GetActivity(newActivityId);
}
public async Task MarkDuplicates(IReadOnlyCollection<Activity> activities)
{
foreach (var activity in activities)
{
var duplicates = await _duplicateDetectionService.TryFindDuplicates(activity);
if (duplicates == null || !duplicates.Any()) continue;
var change = new ActivityStateChange
{
ActivityId = activity.Id,
State = ActivityState.Rejected,
ActivityMatchId = duplicates.First().Id,
Note = $"Activity rejected, duplicate of {duplicates.First().Id}."
};
await ApplyStateChange(change);
}
}
public async Task SaveActivityRangeAsync(IReadOnlyCollection<Activity> activities)
{
await _activityRepository.SaveActivityRangeAsync(activities.ToList());
await _unitOfWork.CompleteAsync();
await MarkDuplicates(activities);
}
public async Task<ProcessResult> RunReferralStatusUpdates()
{
try
{
var activityUpdates = await MatchOrExpireReferrals();
var message = $"There were {activityUpdates.Count()} Referral Activity State Updates";
Log.Information(message);
_incentiveProcessResult.MessageStack.Add(message);
}
catch (Exception exception)
{
var message = $"There was an issue with Activity State Updates Process. Exception Message: {exception.Message}.";
_incentiveProcessResult.MessageStack.Add(message);
_incentiveProcessResult.Success = false;
}
return _incentiveProcessResult;
}
public async Task<IReadOnlyCollection<ActivityStateChange>> MatchOrExpireReferrals()
{
var activeReferrals = await _activityRepository.FindUnmatchedReferrals();
var changes = new List<ActivityStateChange>();
foreach (var referral in activeReferrals)
{
var matchedSale = await _activityRepository.FindUnmatchedSaleForReferral(referral);
if (matchedSale != null)
{
var acceptedStateChange = await ApplyAcceptedStateChange(referral, matchedSale);
changes.Add(acceptedStateChange);
if (referral.EmployeeId == matchedSale.EmployeeId
&& referral.Product == Product.NewChecking
&& matchedSale.Product == Product.NewChecking)
{
ActivityStateChange rejectedStateChange = await ApplyRejectedStateChange(referral, matchedSale);
changes.Add(rejectedStateChange);
}
continue;
}
var expireRange = referral.Product != Product.FirstMortgageReferral
? DateTime.Today.Subtract(TimeSpan.FromDays(60))
: DateTime.Today.Subtract(TimeSpan.FromDays(120));
if (referral.SourceCreated < expireRange)
{
var change = new ActivityStateChange
{
ActivityId = referral.Id,
State = ActivityState.Rejected,
Note = "Referral has expired."
};
await ApplyStateChange(change);
changes.Add(change);
}
}
return changes;
}
private async Task<ActivityStateChange> ApplyRejectedStateChange(Activity referral, Activity matchedSale)
{
var creditedEmployee = _userService.GetUserByEmployeeIdAsync(matchedSale.EmployeeId);
var rejectedStateChange = new ActivityStateChange
{
ActivityId = referral.Id,
State = ActivityState.Rejected,
ActivityMatchId = matchedSale.Id,
Note = $"Referral rejected as {creditedEmployee.Result.FullName} is also employee on {matchedSale.Id}."
};
await ApplyStateChange(rejectedStateChange);
return rejectedStateChange;
}
private async Task<ActivityStateChange> ApplyAcceptedStateChange(Activity referral, Activity matchedSale)
{
var acceptedStateChange = new ActivityStateChange
{
ActivityId = referral.Id,
State = ActivityState.Accepted,
ActivityMatchId = matchedSale.Id,
Note = $"Referral was matched to {matchedSale.Id}."
};
await ApplyStateChange(acceptedStateChange);
return acceptedStateChange;
}
public async Task<Activity> ApplyStateChange(ActivityStateChange activityStateChange, bool commitTransaction = true)
{
var existingActivity = await _activityRepository.GetActivityById(activityStateChange.ActivityId);
if (existingActivity == null)
{
throw new NotFoundException($"No activity with Id {activityStateChange.ActivityId} could be found");
}
if (activityStateChange.ActivityMatchId == activityStateChange.ActivityId)
{
throw new Exception("Activity cannot change a state against itself.");
}
if (existingActivity.State == activityStateChange.State &&
(activityStateChange.AcceptedDate == null || existingActivity.Accepted == activityStateChange.AcceptedDate))
{
await _activityChangeRepository.SaveActivityStateChange(activityStateChange);
await _unitOfWork.CompleteAsync();
return existingActivity;
}
existingActivity.State = activityStateChange.State;
if (activityStateChange.AcceptedDate != null)
{
existingActivity.Accepted = (DateTime)activityStateChange.AcceptedDate?.Date.AddHours(12);
}
if (activityStateChange.State == ActivityState.Accepted || activityStateChange.State == ActivityState.Rejected)
{
if (activityStateChange.AcceptedDate == null)
{
existingActivity.Accepted = DateTime.UtcNow.Date.AddHours(12);
}
if (activityStateChange.ActivityMatchId > 0)
{
var matchedActivity = await _activityRepository.GetActivityById(activityStateChange.ActivityMatchId);
if (matchedActivity == null)
{
throw new NotFoundException($"No matched activity with Id {activityStateChange.ActivityMatchId} could be found");
}
existingActivity.MatchingActivityId = activityStateChange.ActivityMatchId;
if (activityStateChange.State == ActivityState.Accepted)
{
matchedActivity.MatchingActivityId = activityStateChange.ActivityId;
}
}
}
await _activityChangeRepository.SaveActivityStateChange(activityStateChange);
if (commitTransaction)
{
await _unitOfWork.CompleteAsync();
}
return existingActivity;
}
public async Task<IReadOnlyCollection<Activity>> GetActivitiesAsync()
{
var activities = await _activityRepository.GetActivitiesAsync();
return activities ?? new List<Activity>();
}
public async Task<PagedResult<Activity>> ListByFilterAsync(ActivityFilter filter)
{
try
{
return await _activityRepository.GetPagedFilteredActivities(filter);
}
catch (Exception ex)
{
Log.Error(ex, $"Failed to retrieve filtered Actions limit: {filter.Limit}, page: {filter.Page}");
throw new ServerErrorException();
}
}
public async Task<IReadOnlyCollection<ActivityStateChange>> GetActivityStateChanges(int activityId)
{
return await _activityChangeRepository.GetActivityChangesByActivityId(activityId);
}
public async Task<IReadOnlyCollection<DepartmentProgress>> GetDepartmentActivityForMonthRangeAsync(
List<UserDepartment> userDepartments,
MonthRange monthRange
)
{
var deptProgress = new List<DepartmentProgress>();
foreach (var dept in userDepartments)
{
var isAssignedInMonthRange = dept.StartDate.Date < monthRange.EndTime.Date &&
(dept.EndDate == null ||
monthRange.StartTime.Date < dept.EndDate.Value.Date);
if (isAssignedInMonthRange)
{
var startDate = dept.StartDate.Date < monthRange.StartTime.Date
? monthRange.StartTime.Date
: dept.StartDate.Date;
var endDate = dept.EndDate == null ? monthRange.EndTime.Date
: dept.EndDate.Value.Date > monthRange.EndTime.Date ? monthRange.EndTime.Date
: dept.EndDate.Value.Date;
var monthRangeOfAssignment = new MonthRange
(
startDate.Year,
startDate.Month,
endDate.Year,
endDate.Month
);
var progress = new DepartmentProgress(dept.DepartmentId, monthRangeOfAssignment);
var departmentActivity = await _activityRepository.FindDepartmentActivityByMonthRange(
progress.DepartmentId, progress.MonthRange);
progress.ApplyActivity(departmentActivity);
deptProgress.Add(progress);
}
}
return deptProgress;
}
public async Task<Activity> GetActivity(int activityId)
{
var activity = await _activityRepository.GetActivityIncludingPayouts(activityId);
if (activity == null)
{
throw new NotFoundException($"No activity with Id {activityId} could be found");
}
return activity;
}
public async Task<Activity> GetActivityForStateUpdate(Activity queryActivity)
{
var activity = await _activityRepository.FindActivityForStateUpdate(queryActivity);
if (activity == null)
{
throw new NotFoundException(
$"No activity with matching query activity could be found. AccountNumber: {queryActivity.Detail.AccountNumber} MemberName:{queryActivity.Detail.MemberName}");
}
return activity;
}
public async Task<Activity> GetActivityForDetailUpdate(Activity queryActivity)
{
return await _activityRepository.FindActivityForDetailUpdate(queryActivity);
}
public async Task ValidateCanApplyActivityStateChange(AppUser appUser, ActivityStateChange activityStateChange)
{
var activity = await _activityRepository.GetActivityIncludingPayouts(activityStateChange.ActivityId);
if (activity == null)
{
throw new NotFoundException($"No activity with Id {activityStateChange.ActivityId} could be found");
}
if (activity.ActivityPayouts != null && activity.ActivityPayouts.Any())
{
throw new BadActivityStateChangeException("Cannot change the state of an activity that has a payout.");
}
if (activityStateChange.AcceptedDate != null)
{
var lastDateNotPaidOut = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1).AddMonths(-1);
if (activityStateChange.AcceptedDate < lastDateNotPaidOut || activityStateChange.AcceptedDate > DateTime.Today.AddMonths(1))
{
throw new BadActivityStateChangeException(
"Cannot change the accepted date of an activity to a date that's already paid out, or is greater than 1 month from now.");
}
}
var required = activity.RequiredToChangeState(activityStateChange.State);
switch (required)
{
case StateChangeRequirement.NotAllowed:
case StateChangeRequirement.SystemOnly:
throw new BadActivityStateChangeException("Activity can not be changed to target state.");
case StateChangeRequirement.AdminOnly when appUser.HasRoleAnywhere(Role.Admin):
case StateChangeRequirement.AdminOrPermission when appUser.HasRoleAnywhere(Role.Admin)
|| await _authorizationService.AppUserCanMakeStateChange(appUser, activity,
activityStateChange.State):
case StateChangeRequirement.Hierarchical
when await _authorizationService.ValidateHierarchicalDepartmentPermission(appUser, Role.DepartmentManagerRoles, activity.DepartmentId)
|| await _authorizationService.AppUserCanMakeStateChange(appUser, activity, activityStateChange.State):
return;
default:
throw new NotAuthorizedException("User does not have the permission to change the activity state.");
}
}
public async Task<ActivityTransferAction> RequestTransfer(ActivityTransferRequest activityTransferRequest)
{
var activity =
await _activityRepository.GetActivityIncludingPayouts(activityTransferRequest.RequestedActivityId);
if (activity == null)
{
throw new NotFoundException(
$"No activity with Id {activityTransferRequest.RequestedActivityId} could be found");
}
if (activity.ActivityPayouts.Any())
{
throw new BadActivityTransferActionException(
"Cannot request a transfer of an activity that has a payout.");
}
if (!ActivityTransferAction.IsEligibleForTransfer(activity.State))
{
throw new BadActivityTransferActionException(
"Cannot request a transfer that isn't pending or approved.");
}
var newAppUser = await _userService.GetActiveUserByEmployeeIdAsync(activityTransferRequest.NewEmployeeId);
var activityTransferAction = new ActivityTransferAction
{
Id = 0,
ActivityStateBeforeTransfer = activity.State,
RequestedActivityId = activity.Id,
State = ActivityTransferState.Requested,
NewAppUserId = newAppUser.Id,
NewActivityDepartmentId = activityTransferRequest.NewDepartmentId,
NewActivityEmployeeId = activityTransferRequest.NewEmployeeId
};
await _activityTransferActionRepository.SaveTransferAsync(activityTransferAction);
var stateChange = new ActivityStateChange
{
ActivityId = activityTransferRequest.RequestedActivityId,
Note = "",
State = ActivityState.PendingTransfer
};
await ApplyStateChange(stateChange);
var initiatingManager = await _userService
.FindDepartmentManagers(activityTransferAction.RequestedActivity.DepartmentId);
var destinationManager = await _userService
.FindDepartmentManagers(activityTransferAction.NewActivityDepartmentId);
var recipients = initiatingManager
.Concat(destinationManager)
.Select(manager => manager.AdEmail)
.Distinct()
.ToList();
await _notificationService.SendTransferRequestInitiatedNotification(activityTransferAction, recipients);
return activityTransferAction;
}
public async Task<ActivityTransferAction> TransferToOwnDepartment(
ActivityTransferRequest activityTransferRequest,
AppUser employee
)
{
var existingActivity =
await _activityRepository.GetActivityIncludingPayouts(activityTransferRequest.RequestedActivityId);
if (existingActivity == null)
{
throw new NotFoundException(
$"No activity with Id {activityTransferRequest.RequestedActivityId} could be found");
}
if (!string.Equals(existingActivity.EmployeeId, employee.EmployeeId) ||
!string.Equals(activityTransferRequest.NewEmployeeId, employee.EmployeeId))
{
throw new NotAuthorizedException(
"This activity does not belong to the requesting user and cannot be transferred.");
}
if (!employee.HasDepartment(activityTransferRequest.NewDepartmentId))
{
throw new BadActivityTransferActionException(
"Cannot request a transfer of an activity to a department that the user is not assigned to.");
}
if (existingActivity.ActivityPayouts.Any())
{
throw new BadActivityTransferActionException(
"Cannot request a transfer of an activity that has a payout.");
}
if (existingActivity.State != ActivityState.Accepted)
{
throw new BadActivityTransferActionException(
"Cannot request a transfer to another assigned department for an activity that isn't accepted.");
}
var newAppUser = await _userService.GetActiveUserByEmployeeIdAsync(activityTransferRequest.NewEmployeeId);
var activityTransferAction = new ActivityTransferAction
{
Id = 0,
ActivityStateBeforeTransfer = existingActivity.State,
RequestedActivityId = existingActivity.Id,
State = ActivityTransferState.Accepted,
NewAppUserId = newAppUser.Id,
NewActivityDepartmentId = activityTransferRequest.NewDepartmentId,
NewActivityEmployeeId = activityTransferRequest.NewEmployeeId
};
var newActivity = Activity.CopyActivityForTransfer(existingActivity, activityTransferAction, employee.AdEmail);
activityTransferAction.NewActivity = newActivity;
await _activityTransferActionRepository.SaveTransferAsync(activityTransferAction);
await _unitOfWork.CompleteAsync();
var newStateChange = new ActivityStateChange
{
ActivityId = newActivity.Id,
ActivityMatchId = existingActivity.Id,
Note = string.Format(TransferredFromString, existingActivity.Id),
State = activityTransferAction.ActivityStateBeforeTransfer
};
await ApplyStateChange(newStateChange);
var stateChange = new ActivityStateChange
{
ActivityId = existingActivity.Id,
ActivityMatchId = newActivity.Id,
Note = string.Format(TransferredToString, newActivity.Id),
State = ActivityState.Transferred
};
await ApplyStateChange(stateChange);
return activityTransferAction;
}
public async Task<ActivityTransferAction> ApproveTransfer(
AppUser appUser,
ActivityTransferApprovalRequest transferApproval
)
{
var activityTransferAction = await GetActivityTransferAction(transferApproval);
var activity = await _activityRepository.GetActivityIncludingPayouts(activityTransferAction.RequestedActivityId);
var targetStateChange = transferApproval.ApprovalState == ActivityTransferState.Accepted
? ActivityState.Accepted
: activityTransferAction.ActivityStateBeforeTransfer;
var canApproveTransferFrom = await _authorizationService.AppUserCanMakeStateChange(appUser, activity, targetStateChange);
var canApproveTransferTo =
await _authorizationService.AppUserCanMakeStateChange(appUser, activity, targetStateChange, activityTransferAction.NewActivityDepartmentId);
if (!canApproveTransferFrom && !canApproveTransferTo)
{
throw new NotAuthorizedException("Employee is not allowed to approve for either department");
}
if (transferApproval.ApprovalState == ActivityTransferState.Rejected)
{
return await RejectTransfer(activityTransferAction);
}
if (transferApproval.ApprovalState == ActivityTransferState.Accepted)
{
var isApprovedByRequestedDepartment =
!string.IsNullOrEmpty(activityTransferAction.RequestedDepartmentApprover);
var isApprovedByNewDepartment = !string.IsNullOrEmpty(activityTransferAction.NewDepartmentApprover);
if (canApproveTransferFrom && !isApprovedByRequestedDepartment)
{
activityTransferAction.RequestedDepartmentApprover = appUser.FullName;
activityTransferAction.RequestedDepartmentApproved = DateTime.Now;
isApprovedByRequestedDepartment = true;
}
if (canApproveTransferTo && !isApprovedByNewDepartment)
{
activityTransferAction.NewDepartmentApprover = appUser.FullName;
activityTransferAction.NewDepartmentApproved = DateTime.Now;
isApprovedByNewDepartment = true;
}
if (isApprovedByRequestedDepartment && isApprovedByNewDepartment)
{
var existingActivity = await _activityRepository
.GetActivityIncludingPayouts(activityTransferAction.RequestedActivityId);
var newActivityEmployee = await _userService
.GetUserByEmployeeIdAsync(activityTransferAction.NewActivityEmployeeId);
var newActivity = Activity.CopyActivityForTransfer(existingActivity, activityTransferAction,
newActivityEmployee.AdEmail);
activityTransferAction.NewActivity = newActivity;
activityTransferAction.State = ActivityTransferState.Accepted;
await _unitOfWork.CompleteAsync();
var newStateChange = new ActivityStateChange
{
ActivityId = newActivity.Id,
ActivityMatchId = existingActivity.Id,
Note = string.Format(TransferredFromString, existingActivity.Id),
State = activityTransferAction.ActivityStateBeforeTransfer
};
await ApplyStateChange(newStateChange);
var stateChange = new ActivityStateChange
{
ActivityId = existingActivity.Id,
ActivityMatchId = newActivity.Id,
Note = string.Format(TransferredToString, newActivity.Id),
State = ActivityState.Transferred
};
await ApplyStateChange(stateChange);
}
else
{
await _unitOfWork.CompleteAsync();
}
}
return activityTransferAction;
}
public async Task<ActivityTransferAction> GetRequestedTransfer(int activityId)
{
return await _activityTransferActionRepository.GetRequestedTransferActionByActivityId(activityId);
}
private async Task<ActivityTransferAction> GetActivityTransferAction(
ActivityTransferApprovalRequest transferApproval
)
{
var activityTransferAction = await _activityTransferActionRepository
.GetTransferActionById(transferApproval.Id);
if (activityTransferAction == null)
{
throw new NotFoundException(
$"No activity transfer action with Id {transferApproval.Id} could be found");
}
if (activityTransferAction.State != ActivityTransferState.Requested)
{
throw new BadActivityTransferActionException("Approval has already been decided for this activity");
}
return activityTransferAction;
}
private async Task<ActivityTransferAction> RejectTransfer(ActivityTransferAction activityTransferAction)
{
activityTransferAction.State = ActivityTransferState.Rejected;
var stateChange = new ActivityStateChange
{
ActivityId = activityTransferAction.RequestedActivityId,
Note = "Activity transfer has been rejected.",
State = activityTransferAction.ActivityStateBeforeTransfer
};
await ApplyStateChange(stateChange);
return activityTransferAction;
}
public async Task CancelPendingTransfersBetween(MonthRange range)
{
var transfers = await _activityTransferActionRepository.GetActiveTransfersForStateAsync(ActivityState.Accepted);
foreach (var transfer in transfers)
{
if (transfer.RequestedActivity.Accepted.Date >= range.StartTime.Date && transfer.RequestedActivity.Accepted.Date <= range.EndTime.Date)
{
await RejectTransfer(transfer);
}
}
}
public async Task<List<IExternalActivity>> FilterExternalActivitiesByExistingSourceId(
List<IExternalActivity> incomingEternalActivities
)
{
var existingActivities =
await _activityRepository.GetActivitiesBySourceId(incomingEternalActivities.Select(x => x.GetSourceId())
.ToList());
return incomingEternalActivities
.Where(newActivity => !existingActivities.Any(existingActivity =>
existingActivity.Product == newActivity.GetProductType() &&
existingActivity.Type == newActivity.GetActivityType() &&
existingActivity.SourceId == newActivity.GetSourceId()))
.ToList();
}
public async Task<AuthorizedActivityCommands> GetActivityAuthorizedCommands(AppUser appUser, int activityId)
{
var activity = await _activityRepository.GetActivityIncludingPayouts(activityId);
if (activity == null)
{
throw new NotFoundException($"No activity with Id {activityId} could be found");
}
var allowedChanges = new List<ActivityState>();
var canApproveTransferFrom = false;
var canApproveTransferTo = false;
var activityTransferAction = await GetRequestedTransfer(activityId);
var targetDepartmentId = activityTransferAction == null || activity.State != ActivityState.PendingTransfer ? 0 : activityTransferAction.NewActivityDepartmentId;
var hasHierarchicalPermission = await _authorizationService.ValidateHierarchicalDepartmentPermission(appUser,
Role.DepartmentManagerRoles, activity.DepartmentId);
foreach (var stateDetail in ActivityStateDetail.ActivityStateDetails)
{
var changeRequirement = activity.RequiredToChangeState(stateDetail.State);
switch (changeRequirement)
{
case StateChangeRequirement.AdminOnly when appUser.HasRoleAnywhere(Role.Admin):
case StateChangeRequirement.AdminOrPermission when appUser.HasRoleAnywhere(Role.Admin)
|| await _authorizationService.AppUserCanMakeStateChange(appUser, activity,
stateDetail.State, targetDepartmentId):
case StateChangeRequirement.Hierarchical when hasHierarchicalPermission
|| await _authorizationService.AppUserCanMakeStateChange(appUser, activity,
stateDetail.State, targetDepartmentId):
allowedChanges.Add(stateDetail.State);
break;
}
}
if (activityTransferAction != null && allowedChanges.Contains(ActivityState.Transferred))
{
canApproveTransferFrom = await _authorizationService.AppUserCanMakeStateChange(appUser, activity, ActivityState.Transferred);
canApproveTransferTo = await _authorizationService.AppUserCanMakeStateChange(appUser, activity, ActivityState.Transferred,
activityTransferAction.NewActivityDepartmentId);
}
allowedChanges = allowedChanges.Distinct().ToList();
return new AuthorizedActivityCommands(allowedChanges.ToArray(), canApproveTransferFrom, canApproveTransferTo);
}
}
@@ -0,0 +1,395 @@
using System.Globalization;
using System.IO;
using LCU.API.Internal.Operations.IncentiveApi.Exceptions;
using LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision;
using CsvHelper;
using CsvHelper.Configuration;
using LCU.API.Internal.Operations.IncentiveApiLib.Extensions;
using LCU.API.Internal.Operations.IncentiveApiLib.Repositories;
using LCU.API.Internal.Operations.IncentiveApi.Models;
using LCU.API.Internal.Operations.IncentiveApi.Models.Allied;
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices;
using LCU.API.Internal.Operations.IncentiveApi.Services.Transformations;
using LCU.Library.Internal.ApiResponse.Services;
using LCU.Library.Internal.Exceptions;
using LCU.Library.Internal.IncentiveApiModels.Results;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.Allied;
public class AlliedGapMbpTransactionImportService : IAlliedGapMbpTransactionImportService
{
private readonly IUserRepository _userRepository;
private readonly IActivityService _activityService;
private readonly IActivityDecisionService _activityDecisionService;
private readonly IActivityDetailService _activityDetailService;
private readonly IDeadActivityService _deadActivityService;
private readonly IFileOperationsApiService _fileOperationsApiService;
private readonly IGapInsuranceActivityService _gapInsuranceActivityService;
private readonly ProcessResult _alliedGapMbpProcessResult;
public AlliedGapMbpTransactionImportService(
IUserRepository userRepository,
IGapInsuranceActivityService gapInsuranceActivityService,
IFileOperationsApiService fileOperationsApiService,
IActivityService activityService,
IActivityDecisionService activityDecisionService,
IActivityDetailService activityDetailService,
IDeadActivityService deadActivityService
)
{
_userRepository = userRepository;
_gapInsuranceActivityService = gapInsuranceActivityService;
_fileOperationsApiService = fileOperationsApiService;
_activityService = activityService;
_activityDecisionService = activityDecisionService;
_activityDetailService = activityDetailService;
_deadActivityService = deadActivityService;
_alliedGapMbpProcessResult = new ProcessResult();
}
public async Task RunAlliedGapMbpProcess(AlliedGapMbpRequest request)
{
try
{
var importedAlliedSales = await ImportTransactions<AlliedSaleRow>(request);
Log.Information($"Imported {importedAlliedSales.Count} Allied Gap/Mbp sales.");
var matchedRemoved = await _gapInsuranceActivityService.MatchOrRemoveGapAccessories();
Log.Information($"Matched/Removed {matchedRemoved} Allied Gap/Mbp records.");
_alliedGapMbpProcessResult.MessageStack.Add($"Imported {importedAlliedSales.Count()} sales, Matched/Removed {matchedRemoved} records for Allied Gap/Mbp Sales processing.");
await QueueReturnResponse.QueueResponse(request, _alliedGapMbpProcessResult.Success, _alliedGapMbpProcessResult.MessageStack);
}
catch (Exception exception)
{
var message = $"There was an issue with handling Import Allied Gap/Mbp Sales Exception Message: {exception.Message}.";
_alliedGapMbpProcessResult.MessageStack.Add(message);
_alliedGapMbpProcessResult.Success = false;
await QueueReturnResponse.QueueResponse(request, _alliedGapMbpProcessResult.Success, _alliedGapMbpProcessResult.MessageStack);
throw new ServiceException(message);
}
}
public async Task<IReadOnlyCollection<Activity>> ImportTransactions<T>(AlliedGapMbpRequest request) where T : AlliedSaleRow
{
var dynamicFileListRequest = request.ToDynamicAlliedGapMbpFileListRequest();
var files = await _fileOperationsApiService.BuildDynamicFileList(dynamicFileListRequest);
var activity = new List<Activity>();
foreach (var fileName in files)
{
var stringContent = await _fileOperationsApiService.GetFileContent($"{request.FinalBasePath}{fileName}");
using var stringReader = new StringReader(stringContent);
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
Delimiter = ",",
HasHeaderRecord = true,
HeaderValidated = null,
MissingFieldFound = null,
IgnoreBlankLines = true
};
using var csv = new CsvReader(stringReader, config);
var records = csv.GetRecords<T>().ToList();
var allActivity = await CreateActivity(records);
activity.AddRange(allActivity);
}
return activity;
}
public async Task<IReadOnlyCollection<Activity>> CreateActivity(IEnumerable<AlliedSaleRow> transactions)
{
var activities = new List<Activity>();
var deadActivities = new List<DeadActivity>();
try
{
foreach (var alliedSaleRow in transactions)
{
var activity = new Activity()
{
Accepted = DateTime.Parse(alliedSaleRow.SaleDate).Date.AddHours(12),
DepartmentId = -1,
Detail = new ActivityDetail()
{
AccountNumber = alliedSaleRow.DetermineAccountNumber(),
AccountStatus = alliedSaleRow.Status,
MemberName = alliedSaleRow.Buyer,
SubAccountNumber = alliedSaleRow.SalesContractNumber
},
EmployeeEmail = null,
EmployeeId = null,
Product = alliedSaleRow.DetermineAlliedProduct(),
SourceCreated = DateTime.Parse(alliedSaleRow.SaleDate).Date.AddHours(12),
SourceId = alliedSaleRow.DetermineAccountNumber(),
SourceSystem = SystemType.Allied,
State = ActivityState.Created,
Type = ActivityType.Sale
};
var employee = await FindEmployee(alliedSaleRow.Issuer.Trim());
if (employee == null)
{
Log.Warning($"Allied Import, No user for {alliedSaleRow.Issuer}. Saving as Dead Activity.");
deadActivities.Add(new DeadActivity(activity,
DeadActivityReason.MissingRequiredField,
$"Allied Import, No user for {alliedSaleRow.Issuer}"));
continue;
}
activity.AppUserId = employee.Id;
activity.EmployeeEmail = employee.AdEmail;
activity.EmployeeId = employee.EmployeeId;
var homeDept = employee.FindHomeDepartment();
if (homeDept == null)
{
Log.Warning($"User has no reliably determinable home department, {employee.EmployeeId}. Saving as Dead Activity.");
deadActivities.Add(new DeadActivity(activity, DeadActivityReason.MissingRequiredField,
$"User has no reliably determinable home department, {employee.EmployeeId}"));
continue;
}
activity.DepartmentId = homeDept.DepartmentId;
activities.Add(activity);
}
foreach (var activity in activities)
{
var activityStateUpdate = await _activityDecisionService.TryDecisionActivity(activity);
activity.State = activityStateUpdate.State;
}
var activitySets = activities.GroupBy(x => new
{
x.DepartmentId,
x.EmployeeEmail,
x.EmployeeId,
x.Product,
x.SourceSystem,
x.Type,
x.Detail.AccountNumber,
x.Detail.SubAccountNumber
});
var updateOrders = new List<ActivityProcessingOrder>();
foreach (var activitySet in activitySets)
{
var existingActivity = await _activityService.GetActivityForDetailUpdate(activitySet.FirstOrDefault());
if (existingActivity?.State != ActivityState.Transferred
&& existingActivity?.State != ActivityState.PendingTransfer
&& existingActivity?.State != ActivityState.Rejected)
{
updateOrders.Add(
new ActivityProcessingOrder
{
ExistingActivity = existingActivity,
ActivityUpdateItems = activitySet.ToList()
}
);
}
}
try
{
await _deadActivityService.SaveDeadActivityRange(deadActivities);
}
catch (Exception ex)
{
Log.Error(ex, "Failed to save dead activities from AlliedTransactionImportService. Dead Activities will be lost");
}
return TryProcessUpdates(updateOrders).GetAwaiter().GetResult().ToList();
}
catch (Exception ex)
{
Log.Error(ex, ex.Message);
}
return new List<Activity>();
}
private async Task<IEnumerable<Activity>> TryProcessUpdates(IEnumerable<ActivityProcessingOrder> activityDetailUpdateOrders)
{
var returnItems = new List<Activity>();
foreach (var activityDetailUpdateOrder in activityDetailUpdateOrders)
{
try
{
var activityProcessingResult = await HandleActivityProcessingOrder(activityDetailUpdateOrder);
if (activityProcessingResult.UpdateCount <= 0)
{
continue;
}
Log.Debug($"{activityProcessingResult.UpdateCount} updates performed on Allied SourceId: {activityProcessingResult.ExistingActivity.SourceId}");
returnItems.Add(activityDetailUpdateOrder.ExistingActivity);
}
catch (Exception ex)
{
Log.Error(ex, ex.Message);
}
}
return returnItems;
}
public ActivityProcessingOrder FilterAndOrderUpdates(ActivityProcessingOrder activityProcessingOrder)
{
activityProcessingOrder.ActivityUpdateItems = activityProcessingOrder.ActivityUpdateItems
.OrderBy(x => x.SourceCreated)
.ThenBy(x => x.Detail.AccountStatus.ToString().Equals("Reinstated", StringComparison.InvariantCultureIgnoreCase))
.ThenBy(x => x.Detail.AccountStatus.ToString().Equals("Cancelled", StringComparison.InvariantCultureIgnoreCase))
.ThenBy(x => x.Detail.AccountStatus.ToString().Equals("Cancelled/Re-Quoted", StringComparison.InvariantCultureIgnoreCase))
.ThenBy(x => x.Detail.AccountStatus.ToString().Equals("Sold", StringComparison.InvariantCultureIgnoreCase))
.ThenBy(x => x.Detail.AccountStatus.ToString().Equals("Flagged", StringComparison.InvariantCultureIgnoreCase))
.ToList();
if (activityProcessingOrder.ExistingActivity == null)
{
return activityProcessingOrder;
}
var startIndex = activityProcessingOrder.ActivityUpdateItems
.FindIndex(x =>
x.Detail.AccountStatus.Equals(activityProcessingOrder.ExistingActivity.Detail.AccountStatus, StringComparison.InvariantCultureIgnoreCase)
);
if (startIndex > 0)
{
activityProcessingOrder.ActivityUpdateItems.RemoveRange(0, startIndex);
}
return activityProcessingOrder;
}
public async Task<ActivityProcessingOrder> HandleActivityProcessingOrder(ActivityProcessingOrder activityProcessingOrder)
{
activityProcessingOrder = FilterAndOrderUpdates(activityProcessingOrder);
while (!activityProcessingOrder.ProcessingComplete)
{
foreach (var updateActivity in activityProcessingOrder.ActivityUpdateItems)
{
try
{
var updateActivityUpdateCount = 0;
if (activityProcessingOrder.ExistingActivity == null)
{
try
{
activityProcessingOrder.ExistingActivity = await _activityService.CreateAndRetrieveActivity(updateActivity);
updateActivityUpdateCount++;
}
catch (NotFoundException nex)
{
Log.Error(nex, $"Failed to create new Allied Activity SourceId:{updateActivity.SourceId}");
await _deadActivityService.SaveDeadActivity(
new DeadActivity(updateActivity, DeadActivityReason.MissingRequiredField, "Failed to create activity due to missing data")
);
}
catch (Exception ex)
{
Log.Error(ex, $"Failed to create new Allied Activity SourceId:{updateActivity.SourceId}");
await _deadActivityService.SaveDeadActivity(new DeadActivity(updateActivity, DeadActivityReason.Unknown, "Failed to create activity"));
}
finally
{
if (activityProcessingOrder.ExistingActivity == null)
{
activityProcessingOrder.ProcessingComplete = true;
Log.Debug($"No existing Allied Activity SourceId: {updateActivity.SourceId} ");
}
}
}
else
{
var activityDetailsUpdates =
activityProcessingOrder.ExistingActivity.Detail.GetPropertyValueVariances(updateActivity.Detail);
foreach (var update in activityDetailsUpdates)
{
await _activityDetailService.SaveActivityDetailUpdate(new ActivityDetailUpdate
{
ActivityDetailId = activityProcessingOrder.ExistingActivity.Detail.Id,
Note = $"{update.ComparedProperty} has changed from {update.PropertyValueOne} to {update.PropertyValueTwo}"
});
activityProcessingOrder.ExistingActivity.Detail.GetType()
.GetProperty(update.ComparedProperty)
?
.SetValue(activityProcessingOrder.ExistingActivity.Detail,
updateActivity.Detail.GetType().GetProperty(update.ComparedProperty)?.GetValue(updateActivity.Detail),
null);
updateActivityUpdateCount++;
}
}
var existingActivityStateUpdate = await _activityDecisionService.TryDecisionActivity(activityProcessingOrder.ExistingActivity);
if (activityProcessingOrder.ExistingActivity.State != existingActivityStateUpdate.State)
{
updateActivityUpdateCount++;
await _activityService.ApplyStateChange(existingActivityStateUpdate);
}
activityProcessingOrder.UpdateCount += updateActivityUpdateCount;
}
catch (Exception ex)
{
Log.Error(ex, $"Failed to update activity details for activity ID:{activityProcessingOrder.ExistingActivity.Id}");
}
}
activityProcessingOrder.ProcessingComplete = true;
}
return activityProcessingOrder;
}
private async Task<AppUser> FindEmployee(string query)
{
try
{
var users = await _userRepository.SearchUsersAsync(new UserSearchRequest()
{
Query = query
});
if (users?.FirstOrDefault() == null || users.Count() > 1)
{
if (query.IndexOf(",", StringComparison.Ordinal) > 0)
{
users = await _userRepository.SearchUsersAsync(new UserSearchRequest()
{
Query = query.Split(",").First()
});
if (users?.FirstOrDefault() == null || users.Count() > 1)
{
return null;
}
}
return null;
}
return users?.FirstOrDefault();
}
catch (Exception ex)
{
Log.Error(ex, "Error when finding user by email in AlliedTransactionImportService");
return null;
}
}
}
@@ -0,0 +1,14 @@
using LCU.API.Internal.Operations.IncentiveApi.Models;
using LCU.API.Internal.Operations.IncentiveApi.Models.Allied;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Requests;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.Allied;
public interface IAlliedGapMbpTransactionImportService
{
Task RunAlliedGapMbpProcess(AlliedGapMbpRequest request);
Task<IReadOnlyCollection<Activity>> ImportTransactions<T>(AlliedGapMbpRequest request) where T : AlliedSaleRow;
Task<IReadOnlyCollection<Activity>> CreateActivity(IEnumerable<AlliedSaleRow> transactions);
ActivityProcessingOrder FilterAndOrderUpdates(ActivityProcessingOrder activityProcessingOrder);
}
@@ -0,0 +1,153 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.API.Internal.Operations.IncentiveApiLib.Repositories;
using LCU.Library.Internal.IncentiveApiModels.Common;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public class AuthorizationService : IAuthorizationService
{
private readonly IDepartmentRepository _departmentRepository;
private readonly IUserAssignmentRepository _userAssignmentRepository;
public AuthorizationService(IDepartmentRepository departmentRepository, IUserAssignmentRepository userAssignmentRepository)
{
_departmentRepository = departmentRepository;
_userAssignmentRepository = userAssignmentRepository;
}
public async Task<bool> AppUserCanMakeStateChange(AppUser appUser, Activity activity, ActivityState targetState, int targetDepartmentId = 0)
{
if (appUser.HasRoleAnywhere(Role.Admin))
{
return await Task.FromResult(true);
}
targetDepartmentId = targetDepartmentId == 0 ? activity.DepartmentId : targetDepartmentId;
var targetDepartmentAppUserPermissions = appUser.ActiveDepartmentsForRange(new MonthRange())
.FirstOrDefault(x => x.DepartmentId == targetDepartmentId)
?.Role.Permissions.ToList();
if (appUser.AdditionalPermissions != null && targetDepartmentAppUserPermissions == null)
{
targetDepartmentAppUserPermissions = new List<Permission>();
targetDepartmentAppUserPermissions.AddRange(appUser.AdditionalPermissions.Select(x => x.Permission));
} else if (appUser.AdditionalPermissions != null)
{
targetDepartmentAppUserPermissions.AddRange(appUser.AdditionalPermissions.Select(x => x.Permission));
}
if (targetDepartmentAppUserPermissions == null || activity.State == targetState)
{
return await Task.FromResult(false);
}
var canExecuteChange = activity.State switch
{
ActivityState.Accepted => targetState switch
{
ActivityState.ManualReview => targetDepartmentAppUserPermissions.Contains(Permission.SetActivityToManualReviewFromAccepted),
ActivityState.Rejected => targetDepartmentAppUserPermissions.Contains(Permission.SetActivityToRejectedFromAccepted),
_ => false
},
ActivityState.ManualReview => targetState switch
{
ActivityState.Accepted => targetDepartmentAppUserPermissions.Contains(Permission.SetActivityToAcceptedFromManualReview),
ActivityState.Rejected => targetDepartmentAppUserPermissions.Contains(Permission.SetActivityToRejectedFromManualReview),
_ => false
},
ActivityState.PendingTransfer => targetState switch
{
ActivityState.Accepted => targetDepartmentAppUserPermissions.Contains(Permission.SetActivityToAcceptedFromPendingTransfer),
ActivityState.Transferred => targetDepartmentAppUserPermissions.Contains(Permission.SetActivityToTransferredFromPendingTransfer),
_ => false
},
ActivityState.Rejected => targetState switch
{
ActivityState.Accepted => targetDepartmentAppUserPermissions.Contains(Permission.SetActivityToAcceptedFromRejected),
ActivityState.ManualReview => targetDepartmentAppUserPermissions.Contains(Permission.SetActivityToManualReviewFromRejected),
_ => false
},
_ => false
};
return canExecuteChange;
}
public async Task<bool> AppUserCanViewReport(AppUser appUser, string permissionCode)
{
if (appUser.HasRoleAnywhere(Role.Admin))
{
return await Task.FromResult(true);
}
var requiredPermission = Permission.LookupPermissionFromCode(permissionCode);
return appUser.UserHasPermission(requiredPermission);
}
public async Task<bool> ValidateHierarchicalDepartmentPermission(
AppUser appUser,
IReadOnlyCollection<Role> allowedDirectRoles,
int departmentId
)
{
return
appUser.HasRoleAnywhere(Role.Admin)
|| appUser.HasAnyRoleForActiveDepartment(departmentId, allowedDirectRoles.ToArray())
|| await ValidateIsBusinessLineManager(appUser, departmentId)
|| await ValidateIsRegionalManager(appUser, departmentId);
}
public async Task<bool> ValidateIsBusinessLineManager(AppUser appUser, int departmentId)
{
var userDept = appUser.FindDepartmentForRole(Role.BusinessLineManager);
if (userDept != null && userDept.Department != null)
{
var bl = BusinessLine.FindByCode(userDept.Department.BusinessLineCode);
if (bl == null)
{
return userDept.Department.Id == departmentId;
}
var departments = await _departmentRepository.FindDepartmentsByBusinessLineAsync(bl);
if (departments.Select(x => x.Id).Contains(departmentId))
{
return true;
}
}
return false;
}
public async Task<bool> ValidateIsRegionalManager(AppUser appUser, int departmentId)
{
var userDept = appUser.FindDepartmentForRole(Role.RegionalManager);
if (userDept != null && userDept.Department != null)
{
var userAssign = await _userAssignmentRepository.IsUserAssigned(AssignmentType.RegionalManager, appUser);
Region rc = null;
if (userAssign != null)
{
rc = Region.FindByCode(userAssign.Key);
}
if (rc == null)
{
rc = Region.FindByCode(userDept.Department.RegionCode);
if (rc == null)
{
return userDept.Department.Id == departmentId;
}
}
var departments = await _departmentRepository.FindDepartmentsByRegionAsync(rc);
if (departments.Select(x => x.Id).Contains(departmentId))
{
return true;
}
}
return false;
}
}
@@ -0,0 +1,413 @@
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;
using LCU.Library.Internal.Exceptions;
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);
throw new ServiceException(message);
}
}
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;
}
}
@@ -0,0 +1,24 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision;
public class CardSaleDecisionStrategy : IDecisionStrategy
{
public bool CanDecideActivity(Activity activity)
{
return (activity.Product == Product.ATMCard || activity.Product == Product.CheckCard)
&& activity.Type == ActivityType.Other;
}
public async Task<ActivityStateChange> DecideActivity(Activity activity)
{
return await Task.FromResult(new ActivityStateChange
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = "Auto-Accept. Will Match with Referral.",
State = ActivityState.Accepted
});
}
}
@@ -0,0 +1,78 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision;
public class DebtProtectionAccessoryService
{
private readonly IActivityService _activityService;
private readonly IActivityDetailService _activityDetailService;
public DebtProtectionAccessoryService(IActivityService activityService, IActivityDetailService activityDetailService)
{
_activityService = activityService;
_activityDetailService = activityDetailService;
}
public async Task<List<Activity>> GetNonRejectedDpAccessoriesWithRejectedMatchedSale(List<Activity> activities)
{
var returnActivities = new List<Activity>();
foreach (var activity in activities)
{
if (activity.MatchingDpAccessoryActivityId != 0)
{
var dpActivity = await _activityService.GetActivity(activity.MatchingDpAccessoryActivityId);
if (dpActivity.State != ActivityState.Rejected)
{
returnActivities.Add(dpActivity);
}
}
}
return returnActivities;
}
public async Task<List<Activity>> ProcessDpAccessoryUpdates(IEnumerable<Activity> saleActivities)
{
var updatedActivities = new List<Activity>();
var filteredSaleActivities = saleActivities.Where(x => x.HasDpAccessory());
foreach (var saleActivity in filteredSaleActivities)
{
if (saleActivity.MatchingDpAccessoryActivityId == 0 && saleActivity.Detail.DebtProtectionAddDate != null)
{
var newDpAccessoryActivity = Activity.CopyActivityForDpAccessory(saleActivity);
saleActivity.MatchingDpAccessoryActivityId = await _activityService.SaveActivityAsync(newDpAccessoryActivity);
var change = new ActivityStateChange
{
ActivityId = saleActivity.Id,
ActivityMatchId = newDpAccessoryActivity.Id,
Note = $"{newDpAccessoryActivity.Product} Added On Activity Id: {newDpAccessoryActivity.Id}",
};
await _activityService.ApplyStateChange(change);
updatedActivities.Add(newDpAccessoryActivity);
}
else
{
if (saleActivity.MatchingDpAccessoryActivityId != 0)
{
var existingDpAccessoryActivity = await _activityService.GetActivity(saleActivity.MatchingDpAccessoryActivityId);
if (existingDpAccessoryActivity.Id != 0 && DpAccessoryRequiresUpdate(saleActivity, existingDpAccessoryActivity))
{
updatedActivities.Add(await _activityDetailService.TryUpdateDpAccessory(saleActivity, existingDpAccessoryActivity));
Log.Information($"After TryUpdateDpAccessory {saleActivity.Id} existingDpAccessoryActivity {existingDpAccessoryActivity.Id})");
}
}
}
}
return updatedActivities;
}
private static bool DpAccessoryRequiresUpdate(Activity saleActivity, Activity dpAccessoryActivity)
{
return saleActivity.Detail.DebtProtectionAddDate?.Date != dpAccessoryActivity.Detail.DebtProtectionAddDate?.Date
|| saleActivity.Detail.DebtProtectionRemoveDate?.Date != dpAccessoryActivity.Detail.DebtProtectionRemoveDate?.Date;
}
}
@@ -0,0 +1,57 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision;
public class DebtProtectionDecisionStrategy : IDecisionStrategy
{
private const Product DecidableProduct = Product.DebtProtection;
public bool CanDecideActivity(Activity activity)
{
return activity.Product == DecidableProduct;
}
public async Task<ActivityStateChange> DecideActivity(Activity activity)
{
if (
activity.MatchingDpAccessoryActivityId != 0
&& activity.Detail.DebtProtectionAddDate != null
&& activity.Detail.DebtProtectionRemoveDate == null
)
{
return await Task.FromResult(new ActivityStateChange
{
AcceptedDate = activity.Detail.DebtProtectionAddDate?.AddHours(12),
ActivityId = activity.Id,
ActivityMatchId = activity.MatchingDpAccessoryActivityId,
Note = $"{DecidableProduct} Added From Activity Id: {activity.MatchingDpAccessoryActivityId}, Auto-Decision: {ActivityState.Accepted}",
State = ActivityState.Accepted
});
}
if (
activity.MatchingDpAccessoryActivityId != 0
&& activity.Detail.DebtProtectionAddDate != null
&& activity.Detail.DebtProtectionRemoveDate != null
)
{
return await Task.FromResult(new ActivityStateChange
{
AcceptedDate = activity.Detail.DebtProtectionRemoveDate?.AddHours(12),
ActivityId = activity.Id,
ActivityMatchId = activity.MatchingDpAccessoryActivityId,
Note = $"Debt Protection Removed From Activity Id: {activity.MatchingDpAccessoryActivityId}, Auto-Decision: {ActivityState.Rejected}",
State = ActivityState.Rejected
});
}
return await Task.FromResult(new ActivityStateChange
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = $"Unable To Auto-Decision: {ActivityState.ManualReview}",
State = ActivityState.ManualReview
});
}
}
@@ -0,0 +1,56 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision;
public class DebtProtectionEligibleLoanDecisionStrategy : IDecisionStrategy
{
public bool CanDecideActivity(Activity activity)
{
return activity.Product == Product.DebtProtectionEligibleLoan && activity.Type == ActivityType.Sale;
}
public async Task<ActivityStateChange> DecideActivity(Activity activity)
{
if (activity.Detail.AccountStatus == null)
{
return await Task.FromResult(new ActivityStateChange()
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = "Cannot Determine Account Status.",
State = ActivityState.Rejected
});
}
if (activity.Detail.AccountStatus.Equals("ACT", StringComparison.InvariantCultureIgnoreCase))
{
return await Task.FromResult(new ActivityStateChange
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = "Account is Active",
State = ActivityState.Accepted
});
}
if (activity.Detail.AccountStatus.Equals("CLS", StringComparison.CurrentCultureIgnoreCase))
{
return await Task.FromResult(new ActivityStateChange
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = "Account is closed.",
State = ActivityState.Rejected
});
}
return await Task.FromResult(new ActivityStateChange
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = "Account is not Closed or Active. Awaiting Manual Review or Status Update.",
State = ActivityState.ManualReview
});
}
}
@@ -0,0 +1,52 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision;
public class GapDecisionStrategy : IDecisionStrategy
{
public bool CanDecideActivity(Activity activity)
{
return activity.Product == Product.GAPInsurance;
}
public async Task<ActivityStateChange> DecideActivity(Activity activity)
{
if (
string.Equals(activity.Detail.AccountStatus, "Flagged", StringComparison.CurrentCultureIgnoreCase)
|| string.Equals(activity.Detail.AccountStatus, "Reinstated", StringComparison.CurrentCultureIgnoreCase)
|| string.Equals(activity.Detail.AccountStatus, "Sold", StringComparison.CurrentCultureIgnoreCase)
)
{
return await Task.FromResult(new ActivityStateChange
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = "Auto-Decision: Gap Accepted",
State = ActivityState.Accepted
});
}
if (
string.Equals(activity.Detail.AccountStatus, "Cancelled", StringComparison.CurrentCultureIgnoreCase)
|| string.Equals(activity.Detail.AccountStatus, "Cancelled/Re-Quoted", StringComparison.CurrentCultureIgnoreCase)
)
{
return await Task.FromResult(new ActivityStateChange
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = $"Auto-Decision: Gap {activity.Detail.AccountStatus}",
State = ActivityState.Rejected
});
}
return await Task.FromResult(new ActivityStateChange
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = "Auto-Decision: Gap Accepted",
State = ActivityState.Accepted
});
}
}
@@ -0,0 +1,18 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.Library.Internal.IncentiveApiModels.Requests;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision
{
public interface IActivityDecisionService
{
Task RunNightlyDecisionProcess(NightlyIncentiveProcessRequest request);
Task<ActivityStateChange> TryDecisionActivity(Activity activity);
Task<IReadOnlyCollection<ActivityStateChange>> DecisionActivityByState(ActivityState state);
Task<IReadOnlyCollection<ActivityStateChange>> UpdateAndDecideManualReviewActivities();
Task<IReadOnlyCollection<ActivityStateChange>> UpdateAndDecideLicReferralActivities();
Task<IReadOnlyCollection<ActivityStateChange>> UpdateAndDecideDpEligibleLoanActivities();
Task<IReadOnlyCollection<ActivityStateChange>> UpdateAndDecideDirectDepositPendingReferralActivities();
Task<List<ActivityStateChange>> AutoDecideActivities(IEnumerable<Activity> activities);
}
}
@@ -0,0 +1,10 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision
{
public interface IDecisionStrategy
{
public bool CanDecideActivity(Activity activity);
public Task<ActivityStateChange> DecideActivity(Activity activity);
}
}
@@ -0,0 +1,31 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision;
public class ManualDecisionStrategy : IDecisionStrategy
{
public bool CanDecideActivity(Activity activity)
{
return activity.Product != Product.ATMCard &&
activity.Product != Product.CheckCard &&
activity.Product != Product.Mortgage &&
activity.Product != Product.NewChecking &&
activity.Product != Product.GAPInsurance &&
activity.Product != Product.MechanicalBreakdownProtection &&
activity.Product != Product.Vehicle &&
activity.Product != Product.DebtProtectionEligibleLoan && (activity.Product == Product.DirectDeposit ||
activity.Product == Product.CreditCardBalanceTransfer ||
activity.Product == Product.LIC) && activity.Type != ActivityType.Referral;
}
public async Task<ActivityStateChange> DecideActivity(Activity activity)
{
return await Task.FromResult(new ActivityStateChange
{
ActivityId = activity.Id,
Note = "Auto-Decision Unavailable: Please Manually Review",
State = ActivityState.ManualReview
});
}
}
@@ -0,0 +1,50 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision;
public class MbpDecisionStrategy : IDecisionStrategy
{
public bool CanDecideActivity(Activity activity)
{
return activity.Product == Product.MechanicalBreakdownProtection;
}
public async Task<ActivityStateChange> DecideActivity(Activity activity)
{
if (
string.Equals(activity.Detail.AccountStatus, "Sold", StringComparison.CurrentCultureIgnoreCase)
|| string.Equals(activity.Detail.AccountStatus, "Flagged", StringComparison.CurrentCultureIgnoreCase)
)
{
return await Task.FromResult(new ActivityStateChange
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = "Auto-Decision: MBP Accepted",
State = ActivityState.Accepted
});
}
if (
string.Equals(activity.Detail.AccountStatus, "Cancelled", StringComparison.CurrentCultureIgnoreCase)
|| string.Equals(activity.Detail.AccountStatus, "Cancelled/Re-Quoted", StringComparison.CurrentCultureIgnoreCase)
)
{
return await Task.FromResult(new ActivityStateChange
{
ActivityId = activity.Id,
Note = $"Auto-Decision: MBP {activity.Detail.AccountStatus}",
State = ActivityState.Rejected
});
}
return await Task.FromResult(new ActivityStateChange
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = "Auto-Decision: MBP Accepted",
State = ActivityState.Accepted
});
}
}
@@ -0,0 +1,58 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision;
public class MortgageSaleDecisionStrategy : IDecisionStrategy
{
public bool CanDecideActivity(Activity activity)
{
return
(activity.Product.Equals(Product.Mortgage) || activity.Product.Equals(Product.FirstMortgageReferral))
&& activity.Type == ActivityType.Sale;
}
public async Task<ActivityStateChange> DecideActivity(Activity activity)
{
if (activity.Detail.AccountStatus == null)
{
return await Task.FromResult(new ActivityStateChange()
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = "Could Not Determine Account Status.",
State = ActivityState.Rejected
});
}
if (activity.Detail.AccountStatus.Equals("ACT", StringComparison.InvariantCultureIgnoreCase))
{
return await Task.FromResult(new ActivityStateChange
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = "Mortgage Loan is Active.",
State = ActivityState.Accepted
});
}
if (activity.Detail.AccountStatus.Equals("CLS", StringComparison.CurrentCultureIgnoreCase))
{
return await Task.FromResult(new ActivityStateChange
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = "Mortgage Loan is closed.",
State = ActivityState.Rejected
});
}
return await Task.FromResult(new ActivityStateChange
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = "Mortgage is not Active or Closed. Waiting for Manual Review or Status Update.",
State = ActivityState.ManualReview
});
}
}
@@ -0,0 +1,67 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision;
public class NewCheckingDecisionStrategy : IDecisionStrategy
{
public bool CanDecideActivity(Activity activity)
{
return activity.Product == Product.NewChecking && activity.Type == ActivityType.Sale;
}
public async Task<ActivityStateChange> DecideActivity(Activity activity)
{
if (activity.Detail.AccountStatus == null)
{
return await Task.FromResult(new ActivityStateChange()
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = "Could Not Determine Account Status",
State = ActivityState.Rejected
});
}
if (activity.Detail.AccountStatus != null && activity.SourceSystem == SystemType.DigitalBanking)
{
return await Task.FromResult(new ActivityStateChange()
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = "Digital Banking Sourced Sale",
State = ActivityState.Accepted
});
}
if (activity.Detail.AccountStatus.Equals("ACT", StringComparison.InvariantCultureIgnoreCase))
{
return await Task.FromResult(new ActivityStateChange
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = "Checking account is Active",
State = ActivityState.Accepted
});
}
if (activity.Detail.AccountStatus.Equals("CLS", StringComparison.InvariantCultureIgnoreCase))
{
return await Task.FromResult(new ActivityStateChange
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = "Checking account is Closed",
State = ActivityState.Rejected
});
}
return await Task.FromResult(new ActivityStateChange
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = "Checking Account is not Active or Closed. Waiting for Manual Review or Status Update",
State = ActivityState.ManualReview
});
}
}
@@ -0,0 +1,70 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision;
public class ReferralDecisionStrategy : IDecisionStrategy
{
public bool CanDecideActivity(Activity activity)
{
return activity.Type == ActivityType.Referral &&
(activity.Product == Product.ATMCard ||
activity.Product == Product.CheckCard ||
activity.Product == Product.NewChecking ||
activity.Product == Product.Mortgage ||
activity.Product == Product.DirectDeposit ||
activity.Product == Product.CreditCardBalanceTransfer ||
activity.Product == Product.LIC ||
activity.Product == Product.FirstMortgageReferral ||
activity.Product == Product.eStatement);
}
public async Task<ActivityStateChange> DecideActivity(Activity activity)
{
if (activity.Product == Product.LIC)
{
if (!string.IsNullOrWhiteSpace(activity.Detail.StationName))
{
if (activity.Detail.StationName.Equals("LIC Referral-Qualified", StringComparison.InvariantCultureIgnoreCase))
{
return await Task.FromResult(new ActivityStateChange()
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = "LIC Referral In Station 'LIC Referral-Qualified'.",
State = ActivityState.Accepted
});
}
if (activity.Detail.StationName.Equals("LIC Referral- Not Qualified", StringComparison.InvariantCultureIgnoreCase))
{
return await Task.FromResult(new ActivityStateChange()
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = "LIC Referral In Station 'LIC Referral- Not Qualified'.",
State = ActivityState.Rejected
});
}
}
return await Task.FromResult(new ActivityStateChange
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = "LIC Referral Not In Final Station. Waiting for Manual Review or Station Update.",
State = ActivityState.ManualReview
});
}
var daysBeforeExpiration = activity.Product != Product.FirstMortgageReferral ? "60" : "120";
return await Task.FromResult(new ActivityStateChange
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = $"Waiting for Matching Sale. Expires in {daysBeforeExpiration} days from creation.",
State = ActivityState.PendingReferral
});
}
}
@@ -0,0 +1,56 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision;
public class VehicleDecisionStrategy : IDecisionStrategy
{
public bool CanDecideActivity(Activity activity)
{
return activity.Product.Equals(Product.Vehicle) && activity.Type == ActivityType.Sale;
}
public async Task<ActivityStateChange> DecideActivity(Activity activity)
{
if (activity.Detail.AccountStatus == null)
{
return await Task.FromResult(new ActivityStateChange()
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = "Could Not Determine Account Status.",
State = ActivityState.Rejected
});
}
if (activity.Detail.AccountStatus.Equals("ACT", StringComparison.InvariantCultureIgnoreCase))
{
return await Task.FromResult(new ActivityStateChange
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = "Vehicle Loan is Active.",
State = ActivityState.Accepted
});
}
if (activity.Detail.AccountStatus.Equals("CLS", StringComparison.CurrentCultureIgnoreCase))
{
return await Task.FromResult(new ActivityStateChange
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = "Vehicle Loan is closed.",
State = ActivityState.Rejected
});
}
return await Task.FromResult(new ActivityStateChange
{
AcceptedDate = activity.Accepted.Date.AddHours(12),
ActivityId = activity.Id,
Note = "Vehicle Loan is not Active or Closed. Waiting for Manual Review or Status Update.",
State = ActivityState.ManualReview
});
}
}
@@ -0,0 +1,49 @@
using LCU.API.Internal.Operations.IncentiveApi.Exceptions;
using LCU.API.Internal.Operations.IncentiveApiLib.Repositories;
using LCU.Library.Internal.IncentiveApiModels.Entities;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public class CommissionService : ICommissionService
{
private readonly ICommissionRepository _commissionRepository;
private readonly IUnitOfWork _unitOfWork;
public CommissionService(ICommissionRepository commissionRepository, IUnitOfWork unitOfWork)
{
_commissionRepository = commissionRepository;
_unitOfWork = unitOfWork;
}
public async Task<IReadOnlyCollection<Commission>> GetCommissionsAsync()
{
return await _commissionRepository.GetCommissionsAsync();
}
public async Task SaveCommissionsAsync(List<Commission> commissionsToSave)
{
foreach (var commission in commissionsToSave)
{
if (commission.Id == 0)
{
var existing = await _commissionRepository.FindExistingCommission(commission.Role, commission.Product);
if (existing != null)
{
throw new CommissionExistsException($"Commission already exists for Role={existing.Role.Name}, Product={existing.Product}");
}
await _commissionRepository.SaveCommission(commission);
}
else
{
var existing = await _commissionRepository.FindExistingCommission(commission.Id);
if (existing != null)
{
existing.RatePerProduct = commission.RatePerProduct;
}
}
}
await _unitOfWork.CompleteAsync();
}
}
@@ -0,0 +1,110 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.API.Internal.Operations.IncentiveApi.Services.Rules;
using LCU.API.Internal.Operations.IncentiveApi.Models;
using LCU.API.Internal.Operations.IncentiveApiLib.Repositories;
using LCU.Library.Internal.IncentiveApiModels.Common;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public class DashboardService : IDashboardService
{
private readonly IDepartmentService _departmentService;
private readonly IActivityRepository _activityRepository;
private readonly IGoalRepository _goalRepository;
private readonly IBusinessRuleService _businessRuleService;
public DashboardService(IDepartmentService departmentService, IActivityRepository activityRepository,
IGoalRepository goalRepository, IBusinessRuleService businessRuleService)
{
_departmentService = departmentService;
_activityRepository = activityRepository;
_goalRepository = goalRepository;
_businessRuleService = businessRuleService;
}
public async Task<RegionProgress> GetRegionProgressForMonthRangeAsync(
Region region, MonthRange monthRange)
{
var departments = await _departmentService.GetDepartmentsByRegionAsync(region);
var regionProgress = new RegionProgress(region, monthRange);
foreach (var dept in departments.Where(x => x.State == DepartmentState.Active))
{
var progress = await GetDepartmentProgressForMonthRangeAsync(dept.Id.Value, monthRange);
regionProgress.Departments.Add(progress);
}
return regionProgress;
}
public async Task<BusinessLineProgress> GetBusinessLineProgressForMonthRangeAsync(
BusinessLine businessLine, MonthRange monthRange)
{
var departments = await _departmentService.GetDepartmentsByBusinessLineAsync(businessLine);
var businessLineProgress = new BusinessLineProgress(businessLine, monthRange);
foreach (var dept in departments.Where(x => x.State == DepartmentState.Active))
{
var progress = await GetDepartmentProgressForMonthRangeAsync(dept.Id.Value, monthRange);
businessLineProgress.Departments.Add(progress);
}
return businessLineProgress;
}
public async Task<EmployeeProgress> GetEmployeeProgressForMonthRangeAsync(AppUser employee, MonthRange monthRange)
{
var users = new List<AppUser>()
{
employee
};
var progress = new EmployeeProgress(employee, monthRange, new List<IPayoutRule>());
foreach (var dept in employee.Departments)
{
var employeeGoals =
await _goalRepository.FindPersonnelGoalsByMonthRange(dept.DepartmentId, users, monthRange);
progress.ApplyGoals(employeeGoals);
}
progress.ApplyAll(await _activityRepository.FindEmployeeActivityByMonthRange(employee, monthRange));
return progress;
}
public async Task<IReadOnlyCollection<DepartmentProgress>> GetDepartmentProgressForMonthRangeAsync(
List<UserDepartment> userDepartments, MonthRange monthRange)
{
var deptProgress = new List<DepartmentProgress>();
foreach (var dept in userDepartments)
{
var perDeptProgress = await GetDepartmentProgressForMonthRangeAsync(dept.DepartmentId, monthRange);
if (perDeptProgress == null) continue;
deptProgress.Add(perDeptProgress);
}
return deptProgress;
}
public async Task<DepartmentProgress> GetDepartmentProgressForMonthRangeAsync(int departmentId,
MonthRange monthRange)
{
var employees =
await _departmentService.FindDepartmentAssignmentsForRange(departmentId, monthRange);
var departmentActivity = await _activityRepository.FindDepartmentActivityByMonthRange(
departmentId, monthRange);
var departmentGoals =
await _goalRepository.FindDepartmentGoalsByMonthRange(departmentId, monthRange);
var progress = new DepartmentProgress(departmentId, monthRange,
employees.Select(x => x.Employee.EmployeeId).Distinct().ToList(),
_businessRuleService.GetRules(PayoutType.Department),
_businessRuleService.GetRules(PayoutType.Personal));
progress.ApplyActivity(departmentActivity);
progress.ApplyGoals(departmentGoals);
var employeeGoals =
await _goalRepository.FindPersonnelGoalsByMonthRange(departmentId, progress.EmployeeList.ToList(),
monthRange);
progress.ApplyGoals(employeeGoals);
return progress;
}
}
@@ -0,0 +1,13 @@
using System.Text.RegularExpressions;
namespace LCU.API.Internal.Operations.IncentiveApiLib.Services;
public static class DataCleaningService
{
public static string CleanedPhoneNumber(string input)
{
var cleanPhoneNumber = input == null ? string.Empty : Regex.Replace(input, @"\D", string.Empty);
cleanPhoneNumber = cleanPhoneNumber.Length == 11 ? cleanPhoneNumber[1..] : cleanPhoneNumber;
return cleanPhoneNumber;
}
}
@@ -0,0 +1,58 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
namespace LCU.API.Internal.Operations.IncentiveApiLib.Services;
public class DataParsingService
{
public static List<ActivityMetaData> MapActivityMetaData(List<string> dataStrings)
{
var parsedMetaData = new List<ActivityMetaData>();
foreach (var dataString in dataStrings)
{
if (!string.IsNullOrWhiteSpace(dataString))
{
foreach (var item in dataString.Split(",", StringSplitOptions.RemoveEmptyEntries))
{
parsedMetaData.Add(DetermineActivityMetaData(item));
}
}
}
return parsedMetaData;
}
public static ActivityMetaData DetermineActivityMetaData(string metaDataString)
{
if (string.IsNullOrWhiteSpace(metaDataString))
{
return null;
}
if (metaDataString.Contains("@"))
{
return new ActivityMetaData
{
MetaData = metaDataString.Trim(),
MetaDataType = MetaDataType.Email
};
}
var phoneString = DataCleaningService.CleanedPhoneNumber(metaDataString);
if (phoneString.Length == 10)
{
return new ActivityMetaData
{
MetaData = phoneString.Trim(),
MetaDataType = MetaDataType.Phone
};
}
return new ActivityMetaData
{
MetaData = metaDataString.Trim(),
MetaDataType = MetaDataType.Unknown
};
}
}
@@ -0,0 +1,78 @@
using LCU.API.Internal.Operations.IncentiveApi.Configuration;
using LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce;
using System.Text;
using System.Net.Http;
using System.Net.Http.Headers;
using Newtonsoft.Json;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.Dayforce;
public class DayForceClientService : IDayForceClientService
{
private readonly DayForceConfiguration _dayForceConfig;
private readonly Uri _baseUri;
public DayForceClientService(DayForceConfiguration dayForceConfig)
{
_dayForceConfig = dayForceConfig;
_baseUri = new Uri(_dayForceConfig.Url + "/" +
_dayForceConfig.DayForceClientName + "/" +
_dayForceConfig.DayForceApiVersion);
}
public async Task<Report> GetReportData(string reportXRefCode)
{
var url = _baseUri.ToString() + "/Reports/" + reportXRefCode;
var response = await GetHttpResponseAsync(url, true);
if (response.IsSuccessStatusCode)
{
try
{
var jsonString = await response.Content.ReadAsStringAsync();
var payload = JsonConvert.DeserializeObject<Payload<Report>>(jsonString);
return payload.Data;
}
catch (Exception ex)
{
Log.Error(ex, "Error Deserializing Dayforce Report");
}
}
else
{
Log.Error("Bad Status Code retrieving report data from Dayforce {0}", response.ToString());
}
return null;
}
public async Task<HttpResponseMessage> GetHttpResponseAsync(string apiUrl, bool attemptRedirect = false)
{
var client = TrustingHttpClient.GetClient(apiUrl);
var request = new HttpRequestMessage(HttpMethod.Get, apiUrl);
request.Headers.Authorization = CreateBasicAuthorizationHeader(request,
_dayForceConfig.DayForceUsername,
_dayForceConfig.DayForcePassword);
var response = await client.SendAsync(request);
if (attemptRedirect)
{
response = await GetHttpResponseAsync(response.RequestMessage.RequestUri.ToString(), false);
}
return response;
}
public AuthenticationHeaderValue CreateBasicAuthorizationHeader(HttpRequestMessage request, string userName,
string password)
{
if (!(string.IsNullOrEmpty(userName) || string.IsNullOrEmpty(password)))
{
return new AuthenticationHeaderValue(
"Basic", Convert.ToBase64String(
Encoding.UTF8.GetBytes(userName + ":" + password)));
}
return null;
}
}
@@ -0,0 +1,293 @@
using LCU.API.Internal.Operations.IncentiveApi.Services.Payout;
using LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce;
using LCU.API.Internal.Operations.IncentiveApiLib.Services;
using LCU.API.Internal.Operations.IncentiveApiLib.Repositories;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Results;
using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices;
using LCU.API.Internal.Operations.IncentiveApi.Configuration;
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.Library.Internal.ApiResponse.Services;
using LCU.Library.Internal.Exceptions;
using ProcessResult = LCU.Library.Internal.IncentiveApiModels.Results.ProcessResult;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.Dayforce;
public class DayForceSyncService : IDayForceSyncService
{
private readonly IUnitOfWork _unitOfWork;
private readonly IUserService _userService;
private readonly INotificationApiService _notificationService;
private readonly IPayoutService _payoutService;
private readonly IDayForceClientService _dayForceClientService;
private readonly ISettingsService _settingsService;
private readonly DayForceConfiguration _dayForceConfig;
private readonly ProcessResult _dayforceSyncProcessResult;
public DayForceSyncService(
IUnitOfWork unitOfWork,
IUserService userService,
INotificationApiService notificationService,
IPayoutService payoutService,
IDayForceClientService dayForceClientService,
ISettingsService settingsService,
DayForceConfiguration dayForceConfig
)
{
_unitOfWork = unitOfWork;
_userService = userService;
_notificationService = notificationService;
_payoutService = payoutService;
_dayForceClientService = dayForceClientService;
_settingsService = settingsService;
_dayForceConfig = dayForceConfig;
_dayforceSyncProcessResult = new ProcessResult();
}
public async Task SyncDayForceUsers(DayforceProcessRequest request)
{
try
{
Log.Information("Starting Dayforce Sync Process for Active Users.");
if (request.MissingRoleNotification == null)
{
request.MissingRoleNotification = _dayForceConfig.DayForceManualReportRecipients;
}
if (request.TerminatedEmployeeNotification == null)
{
request.TerminatedEmployeeNotification = _dayForceConfig.DayForceManualReportRecipients;
}
var activeUsersResult = await SyncActiveDayForceUsers(request.MissingRoleNotification);
Log.Information("Completed Dayforce Sync Process for Active Users.");
_dayforceSyncProcessResult.MessageStack.Add($"Dayforce Sync Process for Active Users complete with {activeUsersResult.Count()}");
Log.Information("Starting Dayforce Sync Process for Terminated Users.");
var terminatedUsersResult = await SyncTerminatedDayForceUsers(request.TerminatedEmployeeNotification);
Log.Information("Completed Dayforce Sync Process for Terminated Users.");
_dayforceSyncProcessResult.MessageStack.Add($"Dayforce Sync Process for Terminated Users complete with {terminatedUsersResult.Count()}");
await QueueReturnResponse.QueueResponse(request, _dayforceSyncProcessResult.Success, _dayforceSyncProcessResult.MessageStack);
}
catch ( Exception exception )
{
Log.Fatal(exception, "There was an Unhandled error Executing Dayforce Sync Process");
var message = $"There was an issue with handling the Dayforce Sync Service Process Exception Message: {exception.Message}.";
_dayforceSyncProcessResult.MessageStack.Add(message);
_dayforceSyncProcessResult.Success = false;
await QueueReturnResponse.QueueResponse(request, _dayforceSyncProcessResult.Success, _dayforceSyncProcessResult.MessageStack);
throw new ServiceException(message);
}
}
public async Task<IReadOnlyCollection<ActiveEmployeeReceipt>> SyncActiveDayForceUsers(List<string> recipients)
{
var report = await _dayForceClientService.GetReportData(_dayForceConfig.DayForceActiveUserReport);
var results = new List<ActiveEmployeeReceipt>();
if (report.Rows == null)
{
throw new Exception("Dayforce returned invalid active report");
}
foreach (var reportRow in report.Rows)
{
var receipt = new ActiveEmployeeReceipt(reportRow);
results.Add(receipt);
try
{
var employee = new ActiveEmployeeResult(reportRow);
receipt.EmployeeRow = employee;
if (!employee.CanProcess())
{
Log.Error("Cannot process dayforce employee. {reportRow}", reportRow.Values);
receipt.Status = ActiveEmployeeReceiptStatus.MISSING_DATA;
continue;
}
var appUser = await _userService.SyncDayForceEmployee(employee);
receipt.AppUser = appUser;
receipt.Status = ActiveEmployeeReceiptStatus.SUCCESS;
if (appUser.Departments.Any(x => x.Role == Role.MISSING_ROLE))
{
receipt.Status = ActiveEmployeeReceiptStatus.MISSING_ROLE;
}
}
catch (Exception ex)
{
Log.Error(ex, "Failed to process dayforce employee. {reportRow}", reportRow.Values);
receipt.Status = ActiveEmployeeReceiptStatus.EXCEPTION;
}
}
var nonExistentRoleAssignments = results
.Where(x => x.Status == ActiveEmployeeReceiptStatus.MISSING_ROLE)
.Select(x => x.AppUser)
.ToList();
if (nonExistentRoleAssignments.Any())
{
var newRoleMappings = nonExistentRoleAssignments.Select(x => x.BaseRole).ToList().Distinct();
await _notificationService.SendMissingRoleNotification(nonExistentRoleAssignments, recipients);
await _settingsService.SaveNewUnknownRoleMappingRangeAsync(newRoleMappings);
}
return results;
}
public async Task<IReadOnlyCollection<TerminatedEmployeeReceipt>> SyncTerminatedDayForceUsers(List<string> recipients)
{
var report = await _dayForceClientService.GetReportData(_dayForceConfig.DayForceTerminatedUserReport);
var terminated = new List<TerminatedEmployeeReceipt>();
if (report.Rows == null)
{
throw new Exception("Dayforce returned invalid termination report");
}
foreach (var reportRow in report.Rows)
{
try
{
var employee = new TerminatedEmployeeResult(reportRow);
var user = await _userService.TerminateDayForceEmployee(employee);
if (user != null)
{
var manager = await _userService.FindManager(user);
var receipt = await _payoutService.CreateTerminatedEmployeePayouts(user, manager);
terminated.Add(receipt);
}
}
catch (Exception ex)
{
Log.Error(ex, "Failed to term dayforce employee. {reportRow}", reportRow.Values);
}
}
if (terminated.Any())
{
await _notificationService.SendTerminatedEmployeeNotification(terminated, recipients);
}
return terminated;
}
public async Task<EmployeeDetailsUpdateResult> UpdateEmployeeDetails(EmployeeDetailsUpdate employeeDetailsUpdate)
{
var currentAppUser = await _userService.GetUserByAdEmailAsync(employeeDetailsUpdate.OldEmail);
if (currentAppUser == null)
{
return new EmployeeDetailsUpdateResult
{
UpdateMessage = $"Employee Details Update Failed, No Current Users Returned From Database"
};
}
if (!string.IsNullOrWhiteSpace(employeeDetailsUpdate.InvalidUpdateData()))
{
return new EmployeeDetailsUpdateResult
{
AppUser = currentAppUser,
UpdateMessage = $"Employee Details Update Failed, {employeeDetailsUpdate.InvalidUpdateData()}"
};
}
var existingUserForNewEmail = await _userService.GetActiveUserByAdEmailAsync(employeeDetailsUpdate.NewEmail);
if (existingUserForNewEmail != null
&& !existingUserForNewEmail.AdEmail.Equals(currentAppUser.AdEmail, StringComparison.InvariantCultureIgnoreCase))
{
return new EmployeeDetailsUpdateResult
{
AppUser = currentAppUser,
UpdateMessage = $"Employee Details Update Failed, Another Active User Already Exists With Email: {employeeDetailsUpdate.NewEmail}"
};
}
var existingUserForNewEmployeeId = await _userService.GetActiveUserByEmployeeIdAsync(employeeDetailsUpdate.NewEmployeeId);
if (existingUserForNewEmployeeId != null
&& !existingUserForNewEmployeeId.EmployeeId.Equals(currentAppUser.EmployeeId, StringComparison.InvariantCultureIgnoreCase))
{
return new EmployeeDetailsUpdateResult
{
AppUser = currentAppUser,
UpdateMessage = $"Employee Details Update Failed, Another Active User Already Exists With EmployeeId: {employeeDetailsUpdate.NewEmployeeId}"
};
}
var hcmUsersMatched = await MatchingHcmUserCount(employeeDetailsUpdate);
if (hcmUsersMatched != 1)
{
return new EmployeeDetailsUpdateResult
{
AppUser = currentAppUser,
UpdateMessage = $"Employee Details Update Failed, {hcmUsersMatched} Users Returned From HCM System Matching New Details"
};
}
return await _userService.UpdateEmployeeDetails(employeeDetailsUpdate);
}
public async Task<IReadOnlyCollection<ActiveEmployeeResult>> GetMatchingHcmUsers(string email = "", string employeeId = "", string fullName = "")
{
if (
string.IsNullOrWhiteSpace(email)
&& string.IsNullOrWhiteSpace(employeeId)
&& string.IsNullOrWhiteSpace(fullName)
)
{
return new List<ActiveEmployeeResult>();
}
var dayForceUsers = await GetAllHcmUsers();
return dayForceUsers.Where(x =>
x.EmployeeNumber.ToString().Equals(employeeId, StringComparison.InvariantCultureIgnoreCase)
|| x.DisplayName.ToString().Equals(fullName, StringComparison.InvariantCultureIgnoreCase)
|| x.LoginName.ToString().Equals(email, StringComparison.InvariantCultureIgnoreCase)
)
.ToList();
}
private async Task<IReadOnlyCollection<ActiveEmployeeResult>> GetAllHcmUsers()
{
var report = await _dayForceClientService.GetReportData(_dayForceConfig.DayForceActiveUserReport);
var results = new List<ActiveEmployeeResult>();
if (report.Rows == null)
{
throw new Exception("Dayforce returned invalid active report");
}
foreach (var reportRow in report.Rows)
{
try
{
var employee = new ActiveEmployeeResult(reportRow);
results.Add(employee);
}
catch (Exception ex)
{
Log.Error(ex, "Failed to parse dayforce employee. {reportRow}", reportRow.Values);
}
}
return results.Where(x => x.CanProcess()).OrderBy(x => x.DisplayName).ToList();
}
private async Task<int> MatchingHcmUserCount(EmployeeDetailsUpdate employeeDetailsUpdate)
{
var dayForceUsers = await GetAllHcmUsers();
var matchingDayforceUsers = dayForceUsers.Where(x =>
x.EmployeeNumber.ToString().Equals(employeeDetailsUpdate.NewEmployeeId, StringComparison.InvariantCultureIgnoreCase)
&& x.DisplayName.ToString().Equals(employeeDetailsUpdate.NewFullName, StringComparison.InvariantCultureIgnoreCase)
&& x.LoginName.ToString().Equals(employeeDetailsUpdate.NewEmail, StringComparison.InvariantCultureIgnoreCase)
)
.ToList();
return matchingDayforceUsers.Count;
}
}
@@ -0,0 +1,9 @@
using System.Threading.Tasks;
using LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.Dayforce;
public interface IDayForceClientService
{
Task<Report> GetReportData(string reportXRefCode);
}
@@ -0,0 +1,14 @@
using LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce;
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.Library.Internal.IncentiveApiModels.Results;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.Dayforce;
public interface IDayForceSyncService
{
Task SyncDayForceUsers(DayforceProcessRequest request);
Task<IReadOnlyCollection<ActiveEmployeeReceipt>> SyncActiveDayForceUsers(List<string> recipients);
Task<IReadOnlyCollection<TerminatedEmployeeReceipt>> SyncTerminatedDayForceUsers(List<string> recipients);
Task<IReadOnlyCollection<ActiveEmployeeResult>> GetMatchingHcmUsers(string email = "", string employeeId = "", string fullName = "");
Task<EmployeeDetailsUpdateResult> UpdateEmployeeDetails(EmployeeDetailsUpdate employeeDetailsUpdate);
}
@@ -0,0 +1,24 @@
using System.Net.Http;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.Dayforce;
public static class TrustingHttpClient
{
public static HttpClient GetClient(string endPoint)
{
var handler = new HttpClientHandler
{
SslProtocols = System.Security.Authentication.SslProtocols.Tls12 |
System.Security.Authentication.SslProtocols.Tls11 |
System.Security.Authentication.SslProtocols.Tls,
ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true
};
var httpClient = new HttpClient(handler)
{
Timeout = TimeSpan.FromSeconds(300),
BaseAddress = new Uri(endPoint)
};
return httpClient;
}
}
@@ -0,0 +1,98 @@
using LCU.API.Internal.Operations.IncentiveApi.Exceptions;
using LCU.API.Internal.Operations.IncentiveApiLib.Repositories;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Common;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public class DeadActivityService : IDeadActivityService
{
private readonly IDeadActivityRepository _deadActivityRepository;
private readonly IUnitOfWork _unitOfWork;
public DeadActivityService(IDeadActivityRepository deadActivityRepository, IUnitOfWork unitOfWork)
{
_deadActivityRepository = deadActivityRepository;
_unitOfWork = unitOfWork;
}
public async Task<DeadActivity> SaveDeadActivity(DeadActivity newDeadActivity)
{
var deadActivity = await SaveDeadActivityRange(new List<DeadActivity>() { newDeadActivity });
await _unitOfWork.CompleteAsync();
return deadActivity.FirstOrDefault();
}
public async Task<List<DeadActivity>> SaveDeadActivityRange(List<DeadActivity> newDeadActivities)
{
var saved = new List<DeadActivity>() { };
if (!newDeadActivities.Any())
{
return saved;
}
var uniqueDeadActivities = await FilterActivitiesByIdentifyingFields(newDeadActivities);
if (uniqueDeadActivities.Any())
{
saved = await _deadActivityRepository.SaveDeadActivityRange(uniqueDeadActivities);
}
await _unitOfWork.CompleteAsync();
return saved;
}
public async Task<DeadActivity> GetDeadActivity(int activityId)
{
var result = await _deadActivityRepository.GetDeadActivityById(activityId);
if (result == null)
{
throw new NotFoundException("Could not find dead activity with that id");
}
return result;
}
public async Task<DeadActivity> ProcessDeadActivity(int deadActivityId, int activityRefId)
{
var result = await GetDeadActivity(deadActivityId);
result.LivingActivityId = activityRefId;
await _unitOfWork.CompleteAsync();
return result;
}
public async Task<List<DeadActivity>> FilterActivitiesByIdentifyingFields(List<DeadActivity> incomingDeadActivities)
{
var existingDeadActivity =
await _deadActivityRepository.GetDeadActivitiesBySourceIds(incomingDeadActivities.Select(x => x.SourceId)
.Where(x => !string.IsNullOrWhiteSpace(x)).ToList());
if (existingDeadActivity!= null && existingDeadActivity.Any())
{
return incomingDeadActivities
.Where(newDeadActivity => !existingDeadActivity.Any(existing =>
newDeadActivity.Product == existing.Product &&
newDeadActivity.Type == existing.Type &&
newDeadActivity.SourceId == existing.SourceId &&
newDeadActivity.SourceSystem == existing.SourceSystem &&
newDeadActivity.EmployeeId == existing.EmployeeId &&
newDeadActivity.EmployeeEmail?.ToLowerInvariant() == existing.EmployeeEmail?.ToLowerInvariant() &&
newDeadActivity.DepartmentId == existing.DepartmentId &&
newDeadActivity.DeadDetail?.AccountNumber == existing.DeadDetail?.AccountNumber)).ToList();
}
return incomingDeadActivities;
}
public async Task<PagedResult<DeadActivity>> ListByFilterAsync(DeadActivityFilter filter)
{
try
{
return await _deadActivityRepository.GetFilteredDeadActivities(filter);
}
catch (Exception ex)
{
Log.Error(ex, $"Failed to retrieve filtered Actions limit: {filter.Limit}, page: {filter.Page}");
throw new ServerErrorException();
}
}
}
@@ -0,0 +1,244 @@
using LCU.API.Internal.Operations.IncentiveApi.Exceptions;
using LCU.API.Internal.Operations.IncentiveApi.Models;
using LCU.API.Internal.Operations.IncentiveApiLib.Repositories;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.Library.Internal.IncentiveApiModels.Common;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public class DepartmentService : IDepartmentService
{
private readonly IDepartmentRepository _departmentRepository;
private readonly IUnitOfWork _unitOfWork;
private readonly IUserAssignmentService _userAssignmentService;
public DepartmentService(IDepartmentRepository departmentRepository,
IUserAssignmentService userAssignmentService, IUnitOfWork unitOfWork)
{
_departmentRepository = departmentRepository;
_userAssignmentService = userAssignmentService;
_unitOfWork = unitOfWork;
}
public async Task<IReadOnlyCollection<Department>> GetAllDepartmentsAsync()
{
try
{
return await _departmentRepository.GetAllDepartmentsAsync();
}
catch (Exception ex)
{
Log.Error(ex, "Failed to Retrieve All Departments");
throw new ServerErrorException();
}
}
public async Task<Department> GetDepartmentByIdAsync(int id)
{
try
{
var department = await _departmentRepository.FindDepartmentByIdAsync(id);
if (department == null)
{
throw new NotFoundException($"Could Not Find Department With Id {id}");
}
return department;
}
catch (NotFoundException)
{
throw;
}
catch (Exception ex)
{
Log.Error(ex, $"Failed to Retrieve Department with Id {id}");
throw new ServerErrorException();
}
}
public async Task<IReadOnlyCollection<Department>> GetDepartmentsByRegionAsync(Region region)
{
try
{
var departments = await _departmentRepository.FindDepartmentsByRegionAsync(region);
if (departments == null)
{
throw new NotFoundException($"Could Not Find Departments for {region.Name}");
}
return departments;
}
catch (NotFoundException)
{
throw;
}
catch (Exception ex)
{
Log.Error(ex, $"Could Not Find Departments for {region.Name}");
throw new ServerErrorException();
}
}
public async Task<IReadOnlyCollection<Department>> GetDepartmentsByBusinessLineAsync(BusinessLine businessLine)
{
var departments = await _departmentRepository.FindDepartmentsByBusinessLineAsync(businessLine);
if (departments == null)
{
throw new NotFoundException($"Could Not Find Departments for {businessLine.Name}");
}
return departments;
}
public async Task<IReadOnlyCollection<UserDepartment>> FindDepartmentAssignmentsForRange(int departmentId,
MonthRange monthRange)
{
return await _departmentRepository.FindDepartmentAssignmentsForRange(departmentId, monthRange);
}
public async Task<Department> GetDepartmentsByDayforceGlCodeAsync(int glCode)
{
var department = await _departmentRepository.GetDepartmentByDayforceGlCode(glCode);
if (department == null)
{
throw new NotFoundException($"Could Not Find Department for {glCode}");
}
return department;
}
public async Task<IReadOnlyCollection<Department>> GetHierarchicalDepartmentsForEmployeeAsync(AppUser employee, bool includeManagers)
{
try
{
if (employee.Departments.Any(x => x.Role == Role.Admin))
{
return await GetAllDepartmentsAsync();
}
var userDept = employee.Departments.FirstOrDefault(x => x.Role == Role.RegionalManager);
if (userDept != null && userDept.Department != null && !string.IsNullOrWhiteSpace(userDept.Department.RegionCode))
{
var region = Region.FindByCode(userDept.Department.RegionCode);
if (region != null)
{
return await _departmentRepository.FindDepartmentsByRegionAsync(region);
}
}
var includedRoles = Role.LeadershipRoles;
if (includeManagers)
{
includedRoles = Role.DepartmentManagerRoles;
}
return employee.Departments
.Where(x => includedRoles.Contains(x.Role))
.Select(x => x.Department).ToList();
}
catch (Exception ex)
{
Log.Error(ex, "Failed to Retrieve departments user can manage");
throw new ServerErrorException();
}
}
public async Task<Department> UpdateDepartmentAsync(Department department, int id)
{
try
{
if (!await IsDepartmentNameUnique(department.DepartmentName, id))
{
Log.Debug("Attempted to add department with name that already exists.");
throw new DuplicateDataException("DepartmentName");
}
var existingDepartment = await _departmentRepository.FindDepartmentByIdAsync(id);
if (existingDepartment == null)
{
throw new NotFoundException($"Could Not Find Department With Id {id}");
}
existingDepartment.DepartmentName = department.DepartmentName;
existingDepartment.State = department.State;
existingDepartment.BusinessLineCode = department.BusinessLineCode;
existingDepartment.RegionCode = department.RegionCode;
await _unitOfWork.CompleteAsync();
return existingDepartment;
}
catch (DuplicateDataException)
{
throw;
}
catch (Exception ex)
{
Log.Error(ex, $"Failed to Update Department with Id {id}");
throw new ServerErrorException();
}
}
public async Task<Department> SaveDepartmentAsync(Department newDepartment)
{
try
{
if (!await IsDepartmentNameUnique(newDepartment.DepartmentName))
{
Log.Debug("Attempted to add department with name that already exists.");
throw new DuplicateDataException("DepartmentName");
}
var department = await _departmentRepository.SaveDepartmentAsync(newDepartment);
await _unitOfWork.CompleteAsync();
return department;
}
catch (DuplicateDataException)
{
throw;
}
catch (Exception ex)
{
Log.Error(ex, $"Failed to save new Department with Name {newDepartment.DepartmentName}.");
throw new ServerErrorException();
}
}
public async Task<bool> IsDepartmentNameUnique(string departmentName, int? id = null)
{
var existingDepartments = await _departmentRepository.GetAllDepartmentsAsync();
if (!existingDepartments.Any())
{
return true;
}
return !existingDepartments.Where(x => x.Id != id).Any(dept =>
string.Equals(dept.DepartmentName, departmentName, StringComparison.InvariantCultureIgnoreCase));
}
public async Task<DepartmentDetails> GetDepartmentDetailsAsync(int id)
{
var department = await _departmentRepository.FindDepartmentByIdAsync(id);
if (department == null)
{
throw new NotFoundException($"Could Not Find Department With ID {id}");
}
var assignments = await _departmentRepository.FindCurrentDepartmentAssignments(id);
var departmentDetails = new DepartmentDetails
{
Department = department,
Assignments = assignments,
};
if (department.RegionCode != null)
{
var regionalManagerAssignment =
await _userAssignmentService.GetUserAssignment(AssignmentType.RegionalManager,
department.RegionCode);
departmentDetails.RegionalManager = regionalManagerAssignment?.AppUser;
}
return departmentDetails;
}
}
@@ -0,0 +1,286 @@
using LCU.API.Internal.Operations.IncentiveApi.Extensions;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.API.Internal.Operations.IncentiveApi.Models;
using LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal;
using LCU.API.Internal.Operations.IncentiveApiLib.Services;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.DispersalStrategy;
public class DispersalStrategyService : IDispersalStrategyService
{
private readonly IUserService _userService;
public DispersalStrategyService(IUserService userService)
{
_userService = userService;
}
public GoalDispersalReceipt ExtrapolateFromExistingGoals(ExtrapolationGoalDispersalData data)
{
var receipt = new GoalDispersalReceipt(data.DepartmentId, GoalDispersalStrategy.ExtrapolateFromExistingGoals);
foreach (var (employeeId, modelEmployeeProductGoals) in data.ModelPersonnelGoals.Goals)
{
var employee = data.DispersalMonthPersonnel.FirstOrDefault(x => x.EmployeeId.Equals(employeeId));
if (employee == null)
{
var missingEmployee =
data.ModelPersonnelGoals.Personnel.FirstOrDefault(x => x.EmployeeId == employeeId);
receipt.AddNewDispersalError(GoalDispersalError.UserNoLongerInDepartment, employeeId,
$"This employee is not assigned to dept: {data.DepartmentId} for the selected dispersal month", missingEmployee?.FullName);
continue;
}
foreach (var (goalProduct, dispersalMonthGoalValue) in data.DispersalMonthDepartmentGoals.ProductGoals)
{
ExtrapolateAndCreateEmployeeGoalChangesForProduct(employee,goalProduct, data, receipt);
}
}
BuildDispersalReceipt(data.DispersalMonthPersonnel, receipt);
CheckForAndLogDuplicates(receipt.GoalChanges, "Extrapolate");
return receipt;
}
private void ExtrapolateAndCreateEmployeeGoalChangesForProduct(AppUser employee, Product product,
ExtrapolationGoalDispersalData data,
GoalDispersalReceipt receipt)
{
var dispersalMonthGoalValue = data.DispersalMonthDepartmentGoals.ProductGoals[product];
var dispersalEmployeePerProductGoal = data.DispersalMonthPersonnelGoals.Goals[employee.EmployeeId];
var modelEmployeePerProductGoal = data.ModelPersonnelGoals.Goals[employee.EmployeeId];
try
{
double modelPersonnelValue = modelEmployeePerProductGoal.ProductGoals[product];
double dispersalPersonnelValue = dispersalEmployeePerProductGoal.ProductGoals[product];
double modelDeptGoalValue = data.ModelDepartmentGoals.ProductGoals[product];
int newValue;
if (modelDeptGoalValue == 0)
{
newValue = 0;
}
else
{
newValue = (int) Math.Ceiling(modelPersonnelValue / modelDeptGoalValue * dispersalMonthGoalValue);
}
if (newValue == (int) dispersalPersonnelValue)
{
return;
}
receipt.GoalChanges.Add(new GoalChange()
{
AppUserId = employee.Id,
DepartmentId = data.DepartmentId,
EndDate = data.DispersalMonthStartDate.EndOfMonth(),
PersonId = employee.EmployeeId,
Product = product,
StartDate = data.DispersalMonthStartDate,
Value = newValue
});
}
catch (Exception ex)
{
Log.Error(ex,
$"Failed to create goal change for employee {employee.EmployeeId} on product: {product} in department {data.DepartmentId}");
receipt.AddNewDispersalError(GoalDispersalError.ErrorsDuringDisperse, employee.EmployeeId,
$"Failed to create goal change for product: {product}. See Logs.");
}
}
private List<GoalDispersalReceiptItem> BuildDispersalReceipt(List<AppUser> dispersalMonthPersonnel, GoalDispersalReceipt receipt)
{
dispersalMonthPersonnel.ForEach(user =>
{
if (receipt.GoalChanges.Any(x => x.PersonId == user.EmployeeId && x.Value > 0))
{
if (!user.UserHasPermission(Permission.HaveSalesGoals))
{
receipt.AddNewDispersalError(GoalDispersalError.DispersedToUserWithoutPermission,
user.EmployeeId, "User received goals, but does not have HasSalesGoal permission. May require manual update.", user.FullName);
}
}
if(!receipt.GoalChanges.Any(x => x.PersonId == user.EmployeeId && x.Value > 0))
{
if (user.UserHasPermission(Permission.HaveSalesGoals))
{
receipt.AddNewDispersalError(GoalDispersalError.NewUserRequiresManualEntry, user.EmployeeId,
"User did not receive goals, but has HasSalesGoal permission. May require manual update.", user.FullName);
}
}
});
return receipt.GoalDispersalErrors.ToList();
}
public GoalDispersalReceipt DefaultWeightedDispersal(DefaultWeightedGoalDispersalData data)
{
var receipt = new GoalDispersalReceipt(data.DepartmentId, GoalDispersalStrategy.DefaultDisperseWithWeights);
var allRolesInDept = data.DispersalMonthPersonnel.Select(x => x.Departments.First(d => d.DepartmentId == data.DepartmentId).Role).ToList();
foreach (var employee in data.DispersalMonthPersonnel)
{
foreach (var (goalProduct, goalValue) in data.DispersalMonthDepartmentGoals.ProductGoals)
{
try
{
var userDept = employee.Departments.First(x => x.DepartmentId == data.DepartmentId);
receipt.GoalChanges.Add(new GoalChange()
{
AppUserId = employee.Id,
DepartmentId = data.DepartmentId,
EndDate = data.DispersalMonthStartDate.EndOfMonth(),
PersonId = employee.EmployeeId,
Product = goalProduct,
StartDate = data.DispersalMonthStartDate,
Value = CalculateDefaultWeightedValue(userDept.Role, goalValue, allRolesInDept)
});
}
catch (Exception ex)
{
Log.Error(ex,
$"Failed to create goal change for employee {employee.EmployeeId} on product: {goalProduct} in department {data.DepartmentId}");
receipt.AddNewDispersalError(GoalDispersalError.ErrorsDuringDisperse, employee.EmployeeId,
$"Failed to create goal change for product: {goalProduct}. See logs.");
}
}
}
CheckForAndLogDuplicates(receipt.GoalChanges, "DefaultWeight");
return receipt;
}
public int CalculateDefaultWeightedValue(Role role, int goalValue, List<Role> allRolesInDept)
{
if (role == Role.PFO)
{
return (int) Math.Ceiling(goalValue * 0.6 / allRolesInDept.Count(x => x.Equals(Role.PFO)));
}
if (role == Role.BM)
{
return (int) Math.Ceiling(goalValue * 0.25 / allRolesInDept.Count(x => x.Equals(Role.BM)));
}
if (role == Role.ABM)
{
return (int) Math.Ceiling(goalValue * 0.15 / allRolesInDept.Count(x => x.Equals(Role.ABM)));
}
return 0;
}
public GoalDispersalReceipt ExtrapolateSingleDepartmentProduct(DepartmentGoalExtrapolationRequest request, PersonnelGoals departmentPersonnelGoals)
{
var receipt = new GoalDispersalReceipt(request.DepartmentId, GoalDispersalStrategy.ExtrapolateSingleDepartmentProduct);
foreach (var (employeeId, personnelPerProductGoals) in departmentPersonnelGoals.Goals)
{
try
{
var existingEmployeeGoal = personnelPerProductGoals.ProductGoals[request.Product];
int newValue;
if (request.OriginalDepartmentGoal == 0)
{
newValue = existingEmployeeGoal;
}
else
{
newValue = (int)Math.Ceiling((decimal)request.NewDepartmentGoal / request.OriginalDepartmentGoal *
existingEmployeeGoal);
}
if (newValue == existingEmployeeGoal)
{
continue;
}
var appUser = _userService.GetActiveUserByEmployeeIdAsync(employeeId).GetAwaiter().GetResult();
receipt.GoalChanges.Add(new GoalChange()
{
AppUserId = appUser.Id,
PersonId = employeeId,
DepartmentId = request.DepartmentId,
Value = newValue,
Product = request.Product,
StartDate = request.DepartmentGoalStartDate.StartOfMonth(),
EndDate = request.DepartmentGoalEndDate.EndOfMonth()
});
}
catch (Exception ex)
{
Log.Error(ex,
$"Failed to create goal change for employee {employeeId} on product: {request.Product} in department {request.DepartmentId}");
receipt.AddNewDispersalError(GoalDispersalError.ErrorsDuringDisperse, employeeId,
$"Failed to create goal change for product: {request.Product}. See logs.");
}
}
return receipt;
}
public GoalDispersalReceipt
CustomWeightedDispersal(CustomWeightedDispersalRequest request, List<AppUser> personnel, int departmentGoal)
{
var receipt = new GoalDispersalReceipt(request.DepartmentId, GoalDispersalStrategy.CustomWeightedDispersal);
var dispersalDate = new DateTime(request.year, request.month, 01);
var allRolesInDept = personnel
.Select(x => x.Departments.FirstOrDefault(d => d.DepartmentId == request.DepartmentId))
.Where(x => x != null)
.Select(x => x.Role)
.ToList();
foreach (var user in personnel)
{
var userDept = user.Departments.FirstOrDefault(x => x.DepartmentId == request.DepartmentId);
if(userDept == null){continue;}
if (request.DispersalWeightByRole.TryGetValue(userDept.Role.Name, out var percentage))
{
var newValue = (int)Math.Ceiling(departmentGoal * percentage /
allRolesInDept.Count(x =>
x.Name.ToLowerInvariant() == userDept.Role.Name.ToLowerInvariant()));
receipt.GoalChanges.Add(new GoalChange()
{
AppUserId = user.Id,
PersonId = user.EmployeeId,
DepartmentId = request.DepartmentId,
Value = newValue,
Product = request.DispersalProduct,
StartDate = dispersalDate.StartOfMonth(),
EndDate = dispersalDate.EndOfMonth()
});
}
}
CheckForAndLogDuplicates(receipt.GoalChanges, "CustomWeight");
return receipt;
}
private void CheckForAndLogDuplicates(List<GoalChange> goalChanges, string strategy)
{
if (goalChanges == null)
{
return;
}
var duplicateGoals = goalChanges.GroupBy(g => new {
g.StartDate,
g.EndDate,
g.Product,
g.Value,
g.PersonId })
.Where(g => g.Skip(1).Any());
if (duplicateGoals.Any())
{
Log.Information($"Duplicate goals in department {goalChanges.Select(g => g.DepartmentId).FirstOrDefault()} during {strategy} strategy.");
}
}
}
@@ -0,0 +1,17 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.API.Internal.Operations.IncentiveApi.Models;
using LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.DispersalStrategy;
public interface IDispersalStrategyService
{
public GoalDispersalReceipt ExtrapolateFromExistingGoals(ExtrapolationGoalDispersalData data);
public GoalDispersalReceipt DefaultWeightedDispersal(DefaultWeightedGoalDispersalData data);
public int CalculateDefaultWeightedValue(Role userRole, int goalValue, List<Role> allRolesInDept);
public GoalDispersalReceipt ExtrapolateSingleDepartmentProduct(DepartmentGoalExtrapolationRequest request,
PersonnelGoals departmentPersonnelGoals);
public GoalDispersalReceipt CustomWeightedDispersal(CustomWeightedDispersalRequest request,
List<AppUser> personnel, int departmentGoal);
}
@@ -0,0 +1,97 @@
using LCU.API.Internal.Operations.IncentiveApi.Exceptions;
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices;
using LCU.Api.Internal.MaidBotLib.Models;
using LCU.Library.Internal.Exceptions;
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.Library.Internal.IncentiveApiModels.Results;
using LCU.Library.Internal.LCUServiceFactory.Configurations;
using LCU.API.Internal.Operations.IncentiveApi.Services.IncentiveProcesses;
using Microsoft.Extensions.DependencyInjection;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.DnaActivityServices;
public class AtmCheckCardActivityService : IncentiveProcessesService, IIncentiveProcessesService
{
private readonly ICoreApiWrapperService _coreApiWrapperService;
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly ProcessResult _incentiveProcessResult;
public AtmCheckCardActivityService(ServicesFactoryConfiguration servicesFactoryConfiguration,
ICoreApiWrapperService coreApiWrapperService,
IServiceScopeFactory serviceScopeFactory)
: base(servicesFactoryConfiguration)
{
_coreApiWrapperService = coreApiWrapperService;
_serviceScopeFactory = serviceScopeFactory;
_incentiveProcessResult = new ProcessResult();
}
public async Task <ProcessResult> SyncLatestActivitiesFromDna(IncentiveProcessRequest request)
{
try
{
var externalActivities = await ProcessLatestActivity();
var message = $"Added {externalActivities.Count()} Atm Check Card Sales from DNA";
Log.Information(message);
_incentiveProcessResult.MessageStack.Add(message);
}
catch (Exception exception)
{
var message = $"There was an issue with Atm Check Card Sales from DNA process. Exception Message: {exception.Message}.";
_incentiveProcessResult.MessageStack.Add(message);
_incentiveProcessResult.Success = false;
}
return _incentiveProcessResult;
}
public async Task<List<IExternalActivity>> ProcessLatestActivity()
{
try
{
using var scope = _serviceScopeFactory.CreateScope();
var lastUpdate = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().GetLastActivityProcessedData(Process.ImportAtmCheckCardSalesFromDna) ?? throw new MissingProcessorRunHistoryException(Process.ImportAtmCheckCardSalesFromDna));
var allActivities = (await _coreApiWrapperService.GetNewAtmCheckCardSalesActivity(lastUpdate.LastProcessedRecordTimestamp)).ToList();
var filteredActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().GetFilteredExternalActivities(allActivities.Cast<IExternalActivity>().ToList()));
var mappedActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().HandleMapExternalActivities(filteredActivities.ToList(), Product.CheckCard, ActivityType.Other));
if (mappedActivities.Count > 0)
{
foreach (var activity in mappedActivities)
{
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().HandleIncomingActivityDecision(activity, Product.CheckCard, ActivityType.Other));
}
}
if (allActivities.Count < 1)
{
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().UpdateProcessorHistory(Process.ImportAtmCheckCardSalesFromDna, lastUpdate.LastProcessedRecordId, lastUpdate.LastProcessedRecordTimestamp));
return filteredActivities;
}
var latestActivity = allActivities.Where(activity => activity.ActivityDateTime == allActivities.Select(ca => ca.ActivityDateTime).Max()).OrderByDescending(x => x.ActivityNbr).First();
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().UpdateProcessorHistory(Process.ImportAtmCheckCardSalesFromDna, latestActivity.ActivityNbr, latestActivity.ActivityDateTime));
return filteredActivities;
}
catch (ServiceException serviceException)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Error(serviceException, $"Import Check and ATM Cards Failure. " +
$"Failed to Process Latest Check and ATM Cards. Will try again from same starting position on next run.");
throw;
}
catch (Exception exception)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Fatal(exception, $"Import Check and ATM Cards Failure. " +
$"Failed to Process Latest Check and ATM Cards. Will try again from same starting position on next run.");
throw;
}
}
}
@@ -0,0 +1,100 @@
using LCU.API.Internal.Operations.IncentiveApi.Exceptions;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision;
using LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity;
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
using LCU.API.Internal.Operations.IncentiveApi.Services;
namespace LCU.API.Internal.Operations.IncentiveApi.DnaActivityServices;
public class DnaActivityProcessingService : IDnaActivityProcessingService
{
private readonly IActivityService _activityService;
private readonly IActivityDecisionService _activityDecisionService;
private readonly IExternalActivityService _externalActivityService;
private readonly IProcessorHistoryService _processorHistoryService;
private readonly IDeadActivityService _deadActivityService;
public DnaActivityProcessingService(IActivityService activityService, IProcessorHistoryService processorHistoryService,
IActivityDecisionService activityDecisionService, IDeadActivityService deadActivityService, IExternalActivityService externalActivityService)
{
_activityDecisionService = activityDecisionService;
_activityService = activityService;
_externalActivityService = externalActivityService;
_deadActivityService = deadActivityService;
_processorHistoryService = processorHistoryService;
}
public async Task<ProcessorHistoryRecord> GetLastActivityProcessedData(Process processName)
{
var lastActivityProcess = await _processorHistoryService.GetProcessorHistory(processName);
if (lastActivityProcess == null)
{
throw new MissingProcessorRunHistoryException(processName);
}
return lastActivityProcess;
}
public async Task<List<IExternalActivity>> GetFilteredExternalActivities(List<IExternalActivity> activityList)
{
return await _activityService.FilterExternalActivitiesByExistingSourceId(activityList);
}
public async Task<List<Activity>> HandleMapExternalActivities(List<IExternalActivity> externalActivityList, Product productName, ActivityType activityType)
{
var mappedActivities = new List<Activity>();
foreach (var item in externalActivityList)
{
try
{
mappedActivities.Add(await _externalActivityService.TryMapExternalActivity(item));
}
catch (Exception ex)
{
Log.Error(ex, $"Could not map activity for item with product {item.GetProductType()} of type {item.GetActivityType()}. Will Try to save as Dead Activity.");
await _deadActivityService.SaveDeadActivity(new DeadActivity(item.GetSourceId(), SystemType.Dna, DeadActivityReason.FailureToMap, "Total Mapping Failure. Only SourceID will be saved."));
}
}
return mappedActivities;
}
public async Task HandleIncomingActivityDecision(Activity activity, Product productName, ActivityType activityType)
{
try
{
var activityId = await _activityService.SaveActivityAsync(activity);
var savedActivity = await _activityService.GetActivity(activityId);
if (savedActivity.CanAutoDecision())
{
var decision = await _activityDecisionService.TryDecisionActivity(activity);
await _activityService.ApplyStateChange(decision);
}
}
catch (DeadActivityException ex)
{
var deadActivity = new DeadActivity(activity, DeadActivityReason.MissingRequiredField, $"{ex.Message}");
deadActivity = await _deadActivityService.SaveDeadActivity(deadActivity);
if (deadActivity != null)
{
Log.Warning($"Activity from Queue was Incomplete product {productName}-{activityType} and sourceId: {activity.SourceId}. Saving as Dead Activity with ID: {deadActivity.Id}");
}
}
catch (Exception ex)
{
Log.Fatal(ex, $"Failed to save or update an incoming activity: {productName}-{activityType} and sourceId: {activity.SourceId}. This Activity will be lost. Please investigate.");
}
}
public async Task UpdateProcessorHistory(Process process, long lastProcessedId, DateTime lastProcessedDateTime)
{
await _processorHistoryService.SaveProcessorHistory(new ProcessorHistoryRecord()
{
Process = process,
LastSuccessfulRunTime = DateTime.UtcNow,
LastProcessedRecordId = lastProcessedId,
LastProcessedRecordTimestamp = lastProcessedDateTime,
});
}
}
@@ -0,0 +1,96 @@
using LCU.API.Internal.Operations.IncentiveApi.Exceptions;
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices;
using LCU.Api.Internal.MaidBotLib.Models;
using LCU.Library.Internal.Exceptions;
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.Library.Internal.IncentiveApiModels.Results;
using LCU.API.Internal.Operations.IncentiveApi.Services.IncentiveProcesses;
using LCU.Library.Internal.LCUServiceFactory.Configurations;
using Microsoft.Extensions.DependencyInjection;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.DnaActivityServices;
public class DpEligibleLoanSalesActivityService : IncentiveProcessesService, IIncentiveProcessesService
{
private readonly ICoreApiWrapperService _coreApiWrapperService;
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly ProcessResult _incentiveProcessResult;
public DpEligibleLoanSalesActivityService(ServicesFactoryConfiguration servicesFactoryConfiguration,
ICoreApiWrapperService coreApiWrapperService, IServiceScopeFactory serviceScopeFactory)
: base(servicesFactoryConfiguration)
{
_coreApiWrapperService = coreApiWrapperService;
_serviceScopeFactory = serviceScopeFactory;
_incentiveProcessResult = new ProcessResult();
}
public async Task<ProcessResult> SyncLatestActivitiesFromDna(IncentiveProcessRequest request)
{
try
{
var externalActivities = await ProcessLatestActivity();
var message = $"Added {externalActivities.Count()} Debt Protection Eligible Loan Sales from DNA";
Log.Information(message);
_incentiveProcessResult.MessageStack.Add(message);
}
catch(Exception exception)
{
var message = $"There was an issue with Debt Protection Eligible Loan Sales from DNA process. Exception Message: {exception.Message}.";
_incentiveProcessResult.MessageStack.Add(message);
_incentiveProcessResult.Success = false;
}
return _incentiveProcessResult;
}
public async Task<List<IExternalActivity>> ProcessLatestActivity()
{
try
{
using var scope = _serviceScopeFactory.CreateScope();
var lastUpdate = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().GetLastActivityProcessedData(Process.ImportDpEligibleLoanSalesFromDna) ?? throw new MissingProcessorRunHistoryException(Process.ImportDpEligibleLoanSalesFromDna));
var allActivities = (await _coreApiWrapperService.GetNewDpEligibleSaleActivities(lastUpdate.LastProcessedRecordTimestamp)).ToList();
var filteredActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().GetFilteredExternalActivities(allActivities.Cast<IExternalActivity>().ToList()));
var mappedActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().HandleMapExternalActivities(filteredActivities.ToList(), Product.DebtProtectionEligibleLoan, ActivityType.Sale));
if (mappedActivities.Count > 0)
{
foreach (var activity in mappedActivities)
{
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().HandleIncomingActivityDecision(activity, Product.DebtProtectionEligibleLoan, ActivityType.Sale));
}
}
if (allActivities.Count < 1)
{
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().UpdateProcessorHistory(Process.ImportDpEligibleLoanSalesFromDna, lastUpdate.LastProcessedRecordId, lastUpdate.LastProcessedRecordTimestamp));
return filteredActivities;
}
var latestActivity = allActivities.Where(activity => activity.ActivityDateTime == allActivities.Select(ca => ca.ActivityDateTime).Max()).OrderByDescending(x => x.ActivityNbr).First();
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().UpdateProcessorHistory(Process.ImportDpEligibleLoanSalesFromDna, latestActivity.ActivityNbr, latestActivity.ActivityDateTime));
return filteredActivities;
}
catch (ServiceException serviceException)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Error(serviceException, $"Import Debt Protection Eligible Loans Failure. " +
$"Failed to Process Debt Protection Eligible Loans. Will try again from same starting position on next run.");
throw;
}
catch (Exception exception)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Fatal(exception, $"Import Debt Protection Eligible Loans Failure. " +
$"Failed to Process Debt Protection Eligible Loans. Will try again from same starting position on next run.");
throw;
}
}
}
@@ -0,0 +1,14 @@
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public interface IDnaActivityProcessingService
{
Task<ProcessorHistoryRecord> GetLastActivityProcessedData(Process processName);
Task<List<IExternalActivity>> GetFilteredExternalActivities(List<IExternalActivity> activityList);
Task HandleIncomingActivityDecision(Activity activity, Product productName, ActivityType activityType);
Task<List<Activity>> HandleMapExternalActivities(List<IExternalActivity> externalActivityList, Product productName, ActivityType activityType);
Task UpdateProcessorHistory(Process process, long lastProcessedId, DateTime lastProcessedDateTime);
}
@@ -0,0 +1,93 @@
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.Library.Internal.Exceptions;
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
using LCU.Api.Internal.MaidBotLib.Models;
using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices;
using LCU.API.Internal.Operations.IncentiveApi.Exceptions;
using LCU.Library.Internal.IncentiveApiModels.Results;
using LCU.Library.Internal.LCUServiceFactory.Configurations;
using LCU.API.Internal.Operations.IncentiveApi.Services.IncentiveProcesses;
using Microsoft.Extensions.DependencyInjection;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.DnaActivityServices;
public class MortgageSaleActivityService : IncentiveProcessesService, IIncentiveProcessesService
{
private readonly ICoreApiWrapperService _coreApiWrapperService;
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly ProcessResult _incentiveProcessResult;
public MortgageSaleActivityService(ServicesFactoryConfiguration servicesFactoryConfiguration,
ICoreApiWrapperService coreApiWrapperService, IServiceScopeFactory serviceScopeFactory)
: base(servicesFactoryConfiguration)
{
_coreApiWrapperService = coreApiWrapperService;
_serviceScopeFactory = serviceScopeFactory;
_incentiveProcessResult = new ProcessResult();
}
public async Task <ProcessResult> SyncLatestActivitiesFromDna(IncentiveProcessRequest request)
{
try
{
var externalActivities = await ProcessLatestActivity();
var message = $"Added {externalActivities.Count()} Mortgage Accounts from DNA";
Log.Information(message);
_incentiveProcessResult.MessageStack.Add(message);
}
catch (Exception exception)
{
var message = $"There was an issue with Mortgage Loan Accounts from DNA process. Exception Message: {exception.Message}.";
_incentiveProcessResult.MessageStack.Add(message);
_incentiveProcessResult.Success = false;
}
return _incentiveProcessResult;
}
public async Task<List<IExternalActivity>> ProcessLatestActivity()
{
try
{
using var scope = _serviceScopeFactory.CreateScope();
var lastUpdate = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().GetLastActivityProcessedData(Process.ImportMortgageSalesFromDna) ?? throw new MissingProcessorRunHistoryException(Process.ImportMortgageSalesFromDna));
var allActivities = (await _coreApiWrapperService.GetNewMortgageSaleActivities(lastUpdate.LastProcessedRecordTimestamp)).ToList();
var filteredActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().GetFilteredExternalActivities(allActivities.Cast<IExternalActivity>().ToList()));
var mappedActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().HandleMapExternalActivities(filteredActivities.ToList(), Product.Mortgage, ActivityType.Sale));
if (mappedActivities.Count > 0)
{
foreach (var activity in mappedActivities)
{
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().HandleIncomingActivityDecision(activity, Product.Mortgage, ActivityType.Sale));
}
}
if (allActivities.Count < 1)
{
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().UpdateProcessorHistory(Process.ImportMortgageSalesFromDna, lastUpdate.LastProcessedRecordId, lastUpdate.LastProcessedRecordTimestamp));
return filteredActivities;
}
var latestActivity = allActivities.Where(activity => activity.ActivityDateTime == allActivities.Select(ca => ca.ActivityDateTime).Max()).OrderByDescending(x => x.ActivityNbr).First();
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().UpdateProcessorHistory(Process.ImportMortgageSalesFromDna, latestActivity.ActivityNbr, latestActivity.ActivityDateTime));
return filteredActivities;
}
catch (ServiceException serviceException)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Error(serviceException, $"Import Dna Mortgage Accounts Failure. " +
$"Failed to Process Latest Dna Mortgage Accounts. Will try again from same starting position on next run.");
throw;
}
catch (Exception exception)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Fatal(exception, $"Import Dna Mortgage Accounts Failure. " +
$"Failed to Process Latest Dna Mortgage Accounts. Will try again from same starting position on next run.");
throw;
}
}
}
@@ -0,0 +1,95 @@
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices;
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.Api.Internal.MaidBotLib.Models;
using LCU.Library.Internal.Exceptions;
using LCU.Library.Internal.IncentiveApiModels.Results;
using LCU.Library.Internal.LCUServiceFactory.Configurations;
using LCU.API.Internal.Operations.IncentiveApi.Services.IncentiveProcesses;
using Microsoft.Extensions.DependencyInjection;
using LCU.API.Internal.Operations.IncentiveApi.Exceptions;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.DnaActivityServices;
public class NewCheckingAccountActivityService : IncentiveProcessesService, IIncentiveProcessesService
{
private readonly ICoreApiWrapperService _coreApiWrapperService;
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly ProcessResult _incentiveProcessResult;
public NewCheckingAccountActivityService(ServicesFactoryConfiguration servicesFactoryConfiguration,
ICoreApiWrapperService coreApiWrapperService,
IServiceScopeFactory serviceScopeFactory)
: base(servicesFactoryConfiguration)
{
_coreApiWrapperService = coreApiWrapperService;
_serviceScopeFactory = serviceScopeFactory;
_incentiveProcessResult = new ProcessResult();
}
public async Task<ProcessResult> SyncLatestActivitiesFromDna(IncentiveProcessRequest request)
{
try
{
var externalActivities = await ProcessLatestActivity();
var message = $"Added {externalActivities.Count()} Checking Accounts from DNA";
Log.Information(message);
_incentiveProcessResult.MessageStack.Add(message);
}
catch (Exception exception)
{
var message = $"There was an issue with Checking Accounts from DNA process. Exception Message: {exception.Message}.";
_incentiveProcessResult.MessageStack.Add(message);
_incentiveProcessResult.Success = false;
}
return _incentiveProcessResult;
}
public async Task<List<IExternalActivity>> ProcessLatestActivity()
{
try
{
using var scope = _serviceScopeFactory.CreateScope();
var lastUpdate = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().GetLastActivityProcessedData(Process.ImportCheckingAccountSalesFromDna) ?? throw new MissingProcessorRunHistoryException(Process.ImportCheckingAccountSalesFromDna));
var allActivities = (await _coreApiWrapperService.GetNewCheckingAccountSalesActivity(lastUpdate.LastProcessedRecordTimestamp)).ToList();
var filteredActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().GetFilteredExternalActivities(allActivities.Cast<IExternalActivity>().ToList()));
var mappedActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().HandleMapExternalActivities(filteredActivities.ToList(), Product.NewChecking, ActivityType.Sale));
if (mappedActivities.Count > 0)
{
foreach (var activity in mappedActivities)
{
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().HandleIncomingActivityDecision(activity, Product.NewChecking, ActivityType.Sale));
}
}
if (allActivities.Count < 1)
{
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().UpdateProcessorHistory(Process.ImportCheckingAccountSalesFromDna, lastUpdate.LastProcessedRecordId, lastUpdate.LastProcessedRecordTimestamp));
return filteredActivities;
}
var latestActivity = allActivities.Where(activity => activity.ActivityDateTime == allActivities.Select(ca => ca.ActivityDateTime).Max()).OrderByDescending(x => x.ActivityNbr).First();
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().UpdateProcessorHistory(Process.ImportCheckingAccountSalesFromDna, latestActivity.ActivityNbr, latestActivity.ActivityDateTime));
return filteredActivities;
}
catch (ServiceException serviceException)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Error(serviceException, $"Import Checking Accounts Failure. " +
$"Failed to Process Latest Checking Accounts. Will try again from same starting position on next run.");
throw;
}
catch (Exception exception)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group),
Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Fatal(exception, $"Import Checking Accounts Failure. " +
$"Failed to Process Latest Checking Accounts. Will try again from same starting position on next run.");
throw;
}
}
}
@@ -0,0 +1,93 @@
using LCU.API.Internal.Operations.IncentiveApi.Exceptions;
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices;
using LCU.Api.Internal.MaidBotLib.Models;
using LCU.Library.Internal.Exceptions;
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.Library.Internal.IncentiveApiModels.Results;
using LCU.API.Internal.Operations.IncentiveApi.Services.IncentiveProcesses;
using LCU.Library.Internal.LCUServiceFactory.Configurations;
using Microsoft.Extensions.DependencyInjection;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.DnaActivityServices;
public class NewDigitalCheckingAccountActivityService : IncentiveProcessesService, IIncentiveProcessesService
{
private readonly ICoreApiWrapperService _coreApiWrapperService;
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly ProcessResult _incentiveProcessResult;
public NewDigitalCheckingAccountActivityService(ServicesFactoryConfiguration servicesFactoryConfiguration,
ICoreApiWrapperService coreApiWrapperService, IServiceScopeFactory serviceScopeFactory)
: base(servicesFactoryConfiguration)
{
_coreApiWrapperService = coreApiWrapperService;
_serviceScopeFactory = serviceScopeFactory;
_incentiveProcessResult = new ProcessResult();
}
public async Task<ProcessResult> SyncLatestActivitiesFromDna(IncentiveProcessRequest request)
{
try
{
var externalActivities = await ProcessLatestActivity();
var message = $"Added {externalActivities.Count()} Digital Checking Accounts from DNA";
Log.Information(message);
_incentiveProcessResult.MessageStack.Add(message);
}
catch (Exception exception)
{
var message = $"There was an issue with Digital Checking Accounts from DNA process. Exception Message: {exception.Message}.";
_incentiveProcessResult.MessageStack.Add(message);
_incentiveProcessResult.Success = false;
}
return _incentiveProcessResult;
}
public async Task<List<IExternalActivity>> ProcessLatestActivity()
{
try
{
using var scope = _serviceScopeFactory.CreateScope();
var lastUpdate = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().GetLastActivityProcessedData(Process.ImportCheckingAccountSalesFromDigitalBanking) ?? throw new MissingProcessorRunHistoryException(Process.ImportCheckingAccountSalesFromDigitalBanking));
var allActivities = (await _coreApiWrapperService.GetNewCheckingDigitalBankingAccountSalesActivity(lastUpdate.LastProcessedRecordTimestamp)).ToList();
var filteredActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().GetFilteredExternalActivities(allActivities.Cast<IExternalActivity>().ToList()));
var mappedActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().HandleMapExternalActivities(filteredActivities.ToList(), Product.NewChecking, ActivityType.Sale));
if (mappedActivities.Count > 0)
{
foreach (var activity in mappedActivities)
{
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().HandleIncomingActivityDecision(activity, Product.NewChecking, ActivityType.Sale));
}
}
if (allActivities.Count < 1)
{
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().UpdateProcessorHistory(Process.ImportCheckingAccountSalesFromDigitalBanking, lastUpdate.LastProcessedRecordId, lastUpdate.LastProcessedRecordTimestamp));
return filteredActivities;
}
var latestActivity = allActivities.Where(activity => activity.ActivityDateTime == allActivities.Select(ca => ca.ActivityDateTime).Max()).OrderByDescending(x => x.ActivityNbr).First();
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().UpdateProcessorHistory(Process.ImportCheckingAccountSalesFromDigitalBanking, latestActivity.ActivityNbr, latestActivity.ActivityDateTime));
return filteredActivities;
}
catch (ServiceException serviceException)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Error(serviceException, $"Import Digital Checking Accounts Failure. " +
$"Failed to Process Latest Digital Checking Accounts. Will try again from same starting position on next run.");
throw;
}
catch (Exception exception)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Fatal(exception, $"Import Digital Checking Accounts Failure. " +
$"Failed to Process Latest Digital Checking Accounts. Will try again from same starting position on next run.");
throw;
}
}
}
@@ -0,0 +1,93 @@
using LCU.API.Internal.Operations.IncentiveApi.Exceptions;
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices;
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.Api.Internal.MaidBotLib.Models;
using LCU.Library.Internal.Exceptions;
using LCU.Library.Internal.IncentiveApiModels.Results;
using LCU.API.Internal.Operations.IncentiveApi.Services.IncentiveProcesses;
using LCU.Library.Internal.LCUServiceFactory.Configurations;
using Microsoft.Extensions.DependencyInjection;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.DnaActivityServices;
public class ReferralActivityService : IncentiveProcessesService, IIncentiveProcessesService
{
private readonly ICoreApiWrapperService _coreApiWrapperService;
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly ProcessResult _incentiveProcessResult;
public ReferralActivityService(ServicesFactoryConfiguration servicesFactoryConfiguration,
ICoreApiWrapperService coreApiWrapperService, IServiceScopeFactory serviceScopeFactory)
: base(servicesFactoryConfiguration)
{
_coreApiWrapperService = coreApiWrapperService;
_serviceScopeFactory = serviceScopeFactory;
_incentiveProcessResult = new ProcessResult();
}
public async Task<ProcessResult> SyncLatestActivitiesFromDna(IncentiveProcessRequest request)
{
try
{
var externalActivities = await ProcessLatestActivity();
var message = $"Added {externalActivities.Count()} Referrals from DNA";
Log.Information(message);
_incentiveProcessResult.MessageStack.Add(message);
}
catch (Exception exception)
{
var message = $"There was an issue with Referrals from DNA process. Exception Message: {exception.Message}.";
_incentiveProcessResult.MessageStack.Add(message);
_incentiveProcessResult.Success = false;
}
return _incentiveProcessResult;
}
public async Task<List<IExternalActivity>> ProcessLatestActivity()
{
try
{
using var scope = _serviceScopeFactory.CreateScope();
var lastUpdate = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().GetLastActivityProcessedData(Process.ImportActivityManagerReferralsFromDna) ?? throw new MissingProcessorRunHistoryException(Process.ImportActivityManagerReferralsFromDna));
var allActivities = (await _coreApiWrapperService.GetNewActivityManagerReferralsActivity(lastUpdate.LastProcessedRecordTimestamp)).ToList();
var filteredActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().GetFilteredExternalActivities(allActivities.Cast<IExternalActivity>().ToList()));
var mappedActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().HandleMapExternalActivities(filteredActivities.ToList(), Product.Unknown, ActivityType.Referral));
if (mappedActivities.Count > 0)
{
foreach (var activity in mappedActivities)
{
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().HandleIncomingActivityDecision(activity, Product.Unknown, ActivityType.Referral));
}
}
if (allActivities.Count < 1)
{
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().UpdateProcessorHistory(Process.ImportActivityManagerReferralsFromDna, lastUpdate.LastProcessedRecordId, lastUpdate.LastProcessedRecordTimestamp));
return filteredActivities;
}
var latestActivity = allActivities.Where(activity => activity.OriginationDateTime == allActivities.Select(ca => ca.OriginationDateTime).Max()).OrderByDescending(x => x.ActivityId).First();
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().UpdateProcessorHistory(Process.ImportActivityManagerReferralsFromDna, latestActivity.ActivityId, latestActivity.OriginationDateTime));
return filteredActivities;
}
catch (ServiceException serviceException)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Error(serviceException, $"Import Referrals Failure. " +
$"Failed to Process Referrals. Will try again from same starting position on next run.");
throw;
}
catch (Exception exception)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Fatal(exception, $"Import Referrals Failure. " +
$"Failed to Process Referrals. Will try again from same starting position on next run.");
throw;
}
}
}
@@ -0,0 +1,93 @@
using LCU.API.Internal.Operations.IncentiveApi.Exceptions;
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices;
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.Library.Internal.Exceptions;
using LCU.Api.Internal.MaidBotLib.Models;
using LCU.Library.Internal.IncentiveApiModels.Results;
using LCU.API.Internal.Operations.IncentiveApi.Services.IncentiveProcesses;
using LCU.Library.Internal.LCUServiceFactory.Configurations;
using Microsoft.Extensions.DependencyInjection;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.DnaActivityServices;
public class VehicleSalesActivityService : IncentiveProcessesService, IIncentiveProcessesService
{
private readonly ICoreApiWrapperService _coreApiWrapperService;
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly ProcessResult _incentiveProcessResult;
public VehicleSalesActivityService(ServicesFactoryConfiguration servicesFactoryConfiguration,
ICoreApiWrapperService coreApiWrapperService, IServiceScopeFactory serviceScopeFactory)
: base(servicesFactoryConfiguration)
{
_coreApiWrapperService = coreApiWrapperService;
_serviceScopeFactory = serviceScopeFactory;
_incentiveProcessResult = new ProcessResult();
}
public async Task<ProcessResult> SyncLatestActivitiesFromDna(IncentiveProcessRequest request)
{
try
{
var externalActivities = await ProcessLatestActivity();
var message = $"Added {externalActivities.Count()} Vehicle Accounts from DNA";
Log.Information(message);
_incentiveProcessResult.MessageStack.Add(message);
}
catch(Exception exception)
{
var message = $"There was an issue with Vehicle Accounts from DNA process. Exception Message: {exception.Message}.";
_incentiveProcessResult.MessageStack.Add(message);
_incentiveProcessResult.Success = false;
}
return _incentiveProcessResult;
}
public async Task<List<IExternalActivity>> ProcessLatestActivity()
{
try
{
using var scope = _serviceScopeFactory.CreateScope();
var lastUpdate = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().GetLastActivityProcessedData(Process.ImportVehicleSalesFromDna) ?? throw new MissingProcessorRunHistoryException(Process.ImportVehicleSalesFromDna));
var allActivities = (await _coreApiWrapperService.GetNewVehicleSaleActivities(lastUpdate.LastProcessedRecordTimestamp)).ToList();
var filteredActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().GetFilteredExternalActivities(allActivities.Cast<IExternalActivity>().ToList()));
var mappedActivities = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().HandleMapExternalActivities(filteredActivities.ToList(), Product.Vehicle, ActivityType.Sale));
if (mappedActivities.Count > 0)
{
foreach (var activity in mappedActivities)
{
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().HandleIncomingActivityDecision(activity, Product.Vehicle, ActivityType.Sale));
}
}
if (allActivities.Count < 1)
{
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().UpdateProcessorHistory(Process.ImportVehicleSalesFromDna, lastUpdate.LastProcessedRecordId, lastUpdate.LastProcessedRecordTimestamp));
return filteredActivities;
}
var latestActivity = allActivities.Where(activity => activity.ActivityDateTime == allActivities.Select(ca => ca.ActivityDateTime).Max()).OrderByDescending(x => x.ActivityNbr).First();
await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaActivityProcessingService>().UpdateProcessorHistory(Process.ImportVehicleSalesFromDna, latestActivity.ActivityNbr, latestActivity.ActivityDateTime));
return filteredActivities;
}
catch (ServiceException serviceException)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Error(serviceException, $"Import Dna Vehicle Sales Failure. " +
$"Failed to Process Latest Dna Vehicle Sales. Will try again from same starting position on next run.");
throw;
}
catch (Exception exception)
{
Log.ForContext(Enum.GetName(typeof(PropertyEnum), PropertyEnum.Group), Enum.GetName(typeof(GroupEnum), GroupEnum.ITDevelopment))
.Fatal(exception, $"Import Dna Vehicle Sales Failure. " +
$"Failed to Process Latest Dna Vehicle Sales. Will try again from same starting position on next run.");
throw;
}
}
}
@@ -0,0 +1,52 @@
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.Library.Internal.IncentiveApiModels.Results;
using LCU.Library.Internal.LCUServiceFactory.Services;
using LCU.API.Internal.Operations.IncentiveApi.Services.IncentiveProcesses;
using LCU.Library.Internal.ApiResponse.Services;
using LCU.Library.Internal.Exceptions;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public class DnaActivitySyncService : IDnaActivitySyncService
{
private readonly ILcuServiceFactory<IIncentiveProcessesService> _lcuServiceFactory;
private IIncentiveProcessesService _incentiveProcessesService;
private ProcessResult _incentiveProcessResult;
public DnaActivitySyncService(ILcuServiceFactory<IIncentiveProcessesService> lcuServiceFactory)
{
_lcuServiceFactory = lcuServiceFactory;
_incentiveProcessesService = null;
_incentiveProcessResult = new ProcessResult();
}
public async Task HandleProcessRequest(IncentiveProcessRequest request)
{
try
{
ArgumentNullException.ThrowIfNull(request);
_incentiveProcessesService = _lcuServiceFactory.GetService(request.IncentiveProcessType);
ArgumentNullException.ThrowIfNull(_incentiveProcessesService);
var message = $"Queued Request for {request.IncentiveProcessType}";
Log.Information(message);
_incentiveProcessResult = await _incentiveProcessesService.SyncLatestActivitiesFromDna(request);
await QueueReturnResponse.QueueResponse(request, _incentiveProcessResult.Success, _incentiveProcessResult.MessageStack);
}
catch (Exception exception)
{
var message = $"There was an issue with {request.IncentiveProcessType}. Exception Message: {exception.Message}.";
_incentiveProcessResult.MessageStack.Add(message);
_incentiveProcessResult.Success = false;
await QueueReturnResponse.QueueResponse(request, _incentiveProcessResult.Success, _incentiveProcessResult.MessageStack);
throw new ServiceException(message);
}
}
[Obsolete("01/01/2025 - Removed and located in each activity type service.")]
public async Task SyncLatestActivitiesFromDna()
{
}
}
@@ -0,0 +1,42 @@
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices;
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Accounts;
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Activity;
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Transaction;
using Activity = LCU.Library.Internal.IncentiveApiModels.Entities.Activity;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public class DnaService : IDnaService
{
private readonly ICoreApiWrapperService _coreApiWrapperService;
public DnaService(ICoreApiWrapperService coreApiWrapperService)
{
_coreApiWrapperService = coreApiWrapperService;
}
public async Task<List<ActivityAccountDetails>> GetNewAccountDetailsForActivities(List<Activity> activities)
{
var accountNumbers = activities.Select(x => x.Detail.AccountNumber)
.Where(x => !string.IsNullOrEmpty(x) && x.All(char.IsDigit))
.Select(long.Parse)
.ToList();
return await _coreApiWrapperService.GetUpdatedAccountInformation(accountNumbers) as List<ActivityAccountDetails>;
}
public async Task<List<NewActivityManagerReferral>> QueryActivityManagerForActivities(List<string> activityManagerIds)
{
var sourceIds = activityManagerIds
.Where(x => !string.IsNullOrEmpty(x) && x.All(char.IsDigit))
.Select(long.Parse)
.ToList();
return await _coreApiWrapperService.GetActivityManagerActivities(new ActivityQueryRequest(){ActivityIds = sourceIds}) as List<NewActivityManagerReferral>;
}
public async Task<List<TransactionHistory>> GetTransactionHistory(string accountNumber, DateTime startDate, DateTime endDate, List<string> transactionCodes)
{
var res = await _coreApiWrapperService.GetTransactionHistory(accountNumber, startDate, endDate, transactionCodes);
return res?.ToList();
}
}
@@ -0,0 +1,33 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.API.Internal.Operations.IncentiveApiLib.Repositories;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.DuplicateDetection;
public class DefaultDuplicateDetectionStrategy : IDuplicateDetectionStrategy
{
private readonly IActivityRepository _activityRepository;
public DefaultDuplicateDetectionStrategy(IActivityRepository activityRepository)
{
_activityRepository = activityRepository;
}
public bool CanDecideActivity(Activity activity)
{
return activity.Type == ActivityType.Sale || activity.Type == ActivityType.Other;
}
public async Task<IReadOnlyCollection<Activity>> FindDuplicates(Activity activity)
{
var duplicates = new List<Activity>();
var duplicate = await _activityRepository.FindDuplicateSaleAsync(activity);
if (duplicate != null)
{
duplicates.Add(duplicate);
}
return duplicates;
}
}
@@ -0,0 +1,28 @@
using LCU.API.Internal.Operations.IncentiveApi.Exceptions;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.API.Internal.Operations.IncentiveApiLib.Repositories;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.DuplicateDetection;
public class DuplicateDetectionService : IDuplicateDetectionService
{
private readonly IEnumerable<IDuplicateDetectionStrategy> _strategies;
public DuplicateDetectionService(IEnumerable<IDuplicateDetectionStrategy> strategies, IActivityRepository activityRepository)
{
_strategies = strategies;
}
public async Task<IReadOnlyCollection<Activity>> TryFindDuplicates(Activity activity)
{
var strategy = _strategies.FirstOrDefault(x => x.CanDecideActivity(activity));
if (strategy != null)
{
return await strategy.FindDuplicates(activity);
}
throw new NoAvailableStrategy(
$"No Duplicate Detection Strategy available, {activity.Id} - {activity.Product}:{activity.Type}");
}
}
@@ -0,0 +1,8 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.DuplicateDetection;
public interface IDuplicateDetectionService
{
Task<IReadOnlyCollection<Activity>> TryFindDuplicates(Activity activity);
}
@@ -0,0 +1,9 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.DuplicateDetection;
public interface IDuplicateDetectionStrategy
{
public bool CanDecideActivity(Activity activity);
Task<IReadOnlyCollection<Activity>> FindDuplicates(Activity activity);
}
@@ -0,0 +1,33 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.API.Internal.Operations.IncentiveApiLib.Repositories;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.DuplicateDetection;
public class ReferralDuplicateDetectionStrategy : IDuplicateDetectionStrategy
{
private readonly IActivityRepository _activityRepository;
public ReferralDuplicateDetectionStrategy(IActivityRepository activityRepository)
{
_activityRepository = activityRepository;
}
public bool CanDecideActivity(Activity activity)
{
return activity.Type == ActivityType.Referral && activity.SourceSystem != SystemType.Action;
}
public async Task<IReadOnlyCollection<Activity>> FindDuplicates(Activity activity)
{
var duplicates = new List<Activity>();
var duplicate = await _activityRepository.FindDuplicateReferralAsync(activity);
if (duplicate != null)
{
duplicates.Add(duplicate);
}
return duplicates;
}
}
@@ -0,0 +1,32 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.API.Internal.Operations.IncentiveApiLib.Repositories;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.DuplicateDetection;
public class SourceIdDuplicateDetectionStrategy : IDuplicateDetectionStrategy
{
private readonly IActivityRepository _activityRepository;
public SourceIdDuplicateDetectionStrategy(IActivityRepository activityRepository)
{
_activityRepository = activityRepository;
}
public bool CanDecideActivity(Activity activity)
{
return activity.Type == ActivityType.Referral && activity.SourceSystem == SystemType.Action;
}
public async Task<IReadOnlyCollection<Activity>> FindDuplicates(Activity activity)
{
var duplicates = new List<Activity>();
var duplicate = await _activityRepository.FindDuplicateReferralBySourceIdAsync(activity);
if (duplicate != null)
{
duplicates.Add(duplicate);
}
return duplicates;
}
}
@@ -0,0 +1,127 @@
using LCU.API.Internal.Operations.IncentiveApi.Exceptions;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.API.Internal.Operations.IncentiveApiLib.Repositories;
using LCU.API.Internal.Operations.IncentiveApiLib.Services;
using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public class EStatementService : IEStatementService
{
private readonly IProcessorHistoryService _processorHistoryService;
private readonly ICoreApiWrapperService _coreApiWrapperService;
private readonly IActivityService _activityService;
private readonly IActivityRepository _activityRepository;
public EStatementService(
IProcessorHistoryService processorHistoryService,
ICoreApiWrapperService coreApiWrapperService,
IActivityService activityService,
IActivityRepository activityRepository
)
{
_processorHistoryService = processorHistoryService;
_coreApiWrapperService = coreApiWrapperService;
_activityService = activityService;
_activityRepository = activityRepository;
}
public async Task<IReadOnlyCollection<ActivityStateChange>> UpdateOrExpireEStatementReferral()
{
var deliveryMethods = await GetEStatementDeliveryMethods();
var eStatementReferrals = await _activityRepository.FindPendingEStatementsReferrals();
var changes = new List<ActivityStateChange>();
var matchedDeliveryMethods = new List<EStatementDeliveryMethod>();
foreach (var referral in eStatementReferrals)
{
var matchedDeliveryMethod = _activityRepository.FindDeliveryMethodForEStatement(referral, deliveryMethods);
if (matchedDeliveryMethod != null)
{
var change = new ActivityStateChange
{
ActivityId = referral.Id,
State = ActivityState.Accepted,
Note = $"Delivery method is www for account number {matchedDeliveryMethod.AccountNumber}"
};
await _activityService.ApplyStateChange(change);
changes.Add(change);
matchedDeliveryMethods.Add(matchedDeliveryMethod);
await UpdateReferralProperties(referral, matchedDeliveryMethod);
continue;
}
var expireRange = DateTime.Today.Subtract(TimeSpan.FromDays(60));
if (referral.SourceCreated < expireRange)
{
var change = new ActivityStateChange
{
ActivityId = referral.Id,
State = ActivityState.Rejected,
Note = "Referral has expired."
};
await _activityService.ApplyStateChange(change);
changes.Add(change);
}
}
if (matchedDeliveryMethods.Count > 0)
{
await UpdateProcessorHistory(matchedDeliveryMethods);
}
return changes;
}
private async Task UpdateProcessorHistory(List<EStatementDeliveryMethod> matchedDeliveryMethods)
{
await _processorHistoryService.SaveProcessorHistory(new ProcessorHistoryRecord()
{
Process = Process.GetDeliveryMethodsFromDna,
LastSuccessfulRunTime = DateTime.UtcNow,
LastProcessedRecordId = matchedDeliveryMethods.OrderByDescending(x => x.ActivityNumber).First().ActivityNumber,
LastProcessedRecordTimestamp = matchedDeliveryMethods.OrderByDescending(x => x.ActivityDateTime).First().ActivityDateTime
});
}
private async Task UpdateReferralProperties(Activity referral, EStatementDeliveryMethod matchedDeliveryMethod)
{
referral.MetaData = matchedDeliveryMethod.MetaData.UnionBy(referral.MetaData, x => x.MetaData).ToList();
referral.Detail.AccountNumber = string.IsNullOrEmpty(referral.Detail.AccountNumber)
? matchedDeliveryMethod.AccountNumber.ToString() : referral.Detail.AccountNumber;
_activityRepository.UpdateActivityRangeAsync(new List<Activity>() { referral });
}
private async Task<List<EStatementDeliveryMethod>> GetEStatementDeliveryMethods()
{
var lastRun = await _processorHistoryService.GetProcessorHistory(Process.GetDeliveryMethodsFromDna);
var listDeliveryMethods = new List<EStatementDeliveryMethod>();
if (lastRun == null)
{
throw new MissingProcessorRunHistoryException(Process.GetDeliveryMethodsFromDna);
}
var deliveryMethods = await _coreApiWrapperService.GetEStatmentDeliveryMethods(lastRun.LastProcessedRecordTimestamp);
foreach ( var deliveryMethod in deliveryMethods )
{
if ( deliveryMethod != null )
{
deliveryMethod.MetaData = SetMetaData(deliveryMethod.PhoneNumbers, deliveryMethod.EmailAddresses);
listDeliveryMethods.Add( deliveryMethod );
}
}
return listDeliveryMethods;
}
public List<ActivityMetaData> SetMetaData(string phoneNumbers, string emailAddresses)
{
return DataParsingService.MapActivityMetaData(new List<string> { phoneNumbers, emailAddresses });
}
}
@@ -0,0 +1,28 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity;
public class ExternalActivityService : IExternalActivityService
{
private readonly IEnumerable<IExternalActivityStrategy> _strategies;
public ExternalActivityService(IEnumerable<IExternalActivityStrategy> strategies)
{
_strategies = strategies;
}
public async Task<Activity> TryMapExternalActivity(IExternalActivity externalActivity)
{
var strategy = _strategies.FirstOrDefault(x => x.CanMapActivity(externalActivity));
if (strategy != null)
{
return await strategy.MapToActivityManagerActivity(externalActivity);
}
Log.Error($"Failed to find strategy for External Activity for product {externalActivity.GetProductType()}. Falling back to basic method. Some data may be missing.");
var activity = externalActivity.MapToActivity();
return activity;
}
}
@@ -0,0 +1,9 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity;
public interface IExternalActivityService
{
public Task<Activity> TryMapExternalActivity(IExternalActivity externalActivity);
}
@@ -0,0 +1,11 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity;
public interface IExternalActivityStrategy
{
public bool CanMapActivity(IExternalActivity activity);
public Task<Activity> MapToActivityManagerActivity(IExternalActivity activity);
}
@@ -0,0 +1,74 @@
using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer;
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
using LCU.API.Internal.Operations.IncentiveApiLib.Services;
using LCU.API.Internal.Operations.IncentiveApiLib.Repositories;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity;
public class NewIncentiveManagerActivityStrategy : IExternalActivityStrategy
{
private readonly IUserService _userService;
private readonly IDepartmentRepository _departmentRepository;
public NewIncentiveManagerActivityStrategy(IUserService userService, IDepartmentRepository departmentRepository)
{
_userService = userService;
_departmentRepository = departmentRepository;
}
public bool CanMapActivity(IExternalActivity activity)
{
return activity.GetActivityType() == ActivityType.Referral &&
(activity.GetProductType() == Product.ATMCard ||
activity.GetProductType() == Product.CheckCard ||
activity.GetProductType() == Product.NewChecking ||
activity.GetProductType() == Product.Mortgage ||
activity.GetProductType() == Product.DirectDeposit ||
activity.GetProductType() == Product.CreditCardBalanceTransfer ||
activity.GetProductType() == Product.LIC ||
activity.GetProductType() == Product.FirstMortgageReferral ||
activity.GetProductType() == Product.eStatement);
}
public async Task<Activity> MapToActivityManagerActivity(IExternalActivity activity)
{
AppUser appUser = null;
Department department = null;
try
{
if (activity is NewActivityManagerReferral newActivityManagerReferral)
{
if (string.IsNullOrWhiteSpace(newActivityManagerReferral.EmployeeEmail))
{
appUser = await _userService.TryFindSingleUserByName(new UserNameSearchRequest()
{
FirstName = newActivityManagerReferral.OriginatorFirstName,
MiddleInit = newActivityManagerReferral.OriginatorMiddleInitial,
LastName = newActivityManagerReferral.OriginatorLastName
});
}
else
{
appUser = await _userService.GetUserByAdEmailAsync(newActivityManagerReferral.EmployeeEmail);
}
department =
await _departmentRepository.FindDepartmentByBranchName(newActivityManagerReferral
.OriginatingBranchName);
}
}
catch (Exception ex)
{
Log.Error(ex,
$"Failed to get data for NewIncentiveManagerActivity with sourceId: {activity.MapToActivity().SourceId}. Falling back to basic mapping.");
}
var mappedActivity = activity.MapToActivity();
mappedActivity.AppUserId = appUser?.Id ?? Guid.Empty;
mappedActivity.EmployeeId = appUser?.EmployeeId;
mappedActivity.DepartmentId = department?.Id ?? -1;
return mappedActivity;
}
}
@@ -0,0 +1,69 @@
using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer;
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
using LCU.API.Internal.Operations.IncentiveApiLib.Services;
using LCU.API.Internal.Operations.IncentiveApiLib.Repositories;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity;
public class NewAtmCheckCardActivityStrategy : IExternalActivityStrategy
{
private readonly IUserService _userService;
private readonly IDepartmentRepository _departmentRepository;
public NewAtmCheckCardActivityStrategy(IUserService userService, IDepartmentRepository departmentRepository)
{
_userService = userService;
_departmentRepository = departmentRepository;
}
public bool CanMapActivity(IExternalActivity activity)
{
return activity.GetActivityType() == ActivityType.Other && (activity.GetProductType() == Product.ATMCard ||
activity.GetProductType() == Product.CheckCard);
}
public async Task<Activity> MapToActivityManagerActivity(IExternalActivity activity)
{
AppUser appUser = null;
Department department = null;
try
{
if (activity is NewAtmCheckCardActivity atmCheckCardActivity)
{
if (string.IsNullOrWhiteSpace(atmCheckCardActivity.EmployeeEmail))
{
appUser = await _userService.TryFindSingleUserByName(new UserNameSearchRequest()
{
FirstName = atmCheckCardActivity.AssociateFirst,
MiddleInit = atmCheckCardActivity.AssociateMiddleInit,
LastName = atmCheckCardActivity.AssociateLast
});
}
else
{
appUser = await _userService.GetUserByAdEmailAsync(atmCheckCardActivity.EmployeeEmail);
}
}
if (appUser == null)
{
return activity.MapToActivity();
}
department = await _departmentRepository.FindUserHomeDepartment(appUser);
}
catch (Exception ex)
{
Log.Error(ex,
$"Failed to get data for NewAtmCheckCardActivity with sourceId: {activity.MapToActivity().SourceId}. Falling back to basic mapping.");
}
var mappedActivity = activity.MapToActivity();
mappedActivity.AppUserId = appUser?.Id ?? Guid.Empty;
mappedActivity.EmployeeId = appUser?.EmployeeId;
mappedActivity.DepartmentId = department?.Id ?? -1;
return mappedActivity;
}
}
@@ -0,0 +1,72 @@
using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer;
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
using LCU.API.Internal.Operations.IncentiveApiLib.Services;
using LCU.API.Internal.Operations.IncentiveApiLib.Repositories;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity;
public class NewCheckingAccountActivityStrategy : IExternalActivityStrategy
{
private readonly IUserService _userService;
private readonly IDepartmentRepository _departmentRepository;
public NewCheckingAccountActivityStrategy(IUserService userService, IDepartmentRepository departmentRepository)
{
_userService = userService;
_departmentRepository = departmentRepository;
}
public bool CanMapActivity(IExternalActivity activity)
{
return activity.GetActivityType() == ActivityType.Sale &&
activity.GetProductType() == Product.NewChecking;
}
public async Task<Activity> MapToActivityManagerActivity(IExternalActivity activity)
{
AppUser appUser = null;
Department department = null;
try
{
if (activity is NewCheckingAccountActivity newCheckingActivity)
{
if (string.IsNullOrWhiteSpace(newCheckingActivity.EmployeeEmail))
{
appUser = await _userService.TryFindSingleUserByName(new UserNameSearchRequest()
{
FirstName = newCheckingActivity.AssociateFirst,
LastName = newCheckingActivity.AssociateLast,
MiddleInit = newCheckingActivity.AssociateMiddleInit,
DnaOrgNbr = newCheckingActivity.BranchOrgNbr.ToString()
});
}
else
{
appUser = await _userService.GetUserByAdEmailAsync(newCheckingActivity.EmployeeEmail);
}
if (appUser == null)
{
return activity.MapToActivity();
}
department = await _departmentRepository.FindUserHomeDepartment(appUser);
}
}
catch (Exception ex)
{
Log.Error(ex,
$"Failed to get data for NewCheckingAccountActivity with sourceId: {activity.GetSourceId()}. Falling back to basic mapping.");
}
var mappedActivity = activity.MapToActivity();
mappedActivity.AppUserId = appUser?.Id ?? Guid.Empty;
mappedActivity.EmployeeId = appUser?.EmployeeId;
mappedActivity.DepartmentId = department?.Id ?? -1;
return mappedActivity;
}
}
@@ -0,0 +1,97 @@
using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer;
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
using LCU.API.Internal.Operations.IncentiveApiLib.Services;
using LCU.API.Internal.Operations.IncentiveApiLib.Repositories;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.Library.Internal.IncentiveApiModels.Requests;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity;
public class NewDebtProtectionEligibleLoanStrategy : IExternalActivityStrategy
{
private readonly IUserService _userService;
private readonly IDepartmentRepository _departmentRepository;
public NewDebtProtectionEligibleLoanStrategy(IUserService userService, IDepartmentRepository departmentRepository)
{
_userService = userService;
_departmentRepository = departmentRepository;
}
public bool CanMapActivity(IExternalActivity activity)
{
return activity.GetActivityType() == ActivityType.Sale && activity.GetProductType() == Product.DebtProtectionEligibleLoan;
}
public async Task<Activity> MapToActivityManagerActivity(IExternalActivity activity)
{
AppUser appUser = null;
Department department = null;
try
{
if (activity is NewLoanSaleActivity newDpEligibleLoanSale)
{
if (string.IsNullOrWhiteSpace(newDpEligibleLoanSale.EmployeeEmail))
{
appUser = await _userService.TryFindSingleUserByName(new UserNameSearchRequest()
{
FirstName = newDpEligibleLoanSale.AssociateFirst,
LastName = newDpEligibleLoanSale.AssociateLast,
MiddleInit = newDpEligibleLoanSale.AssociateMiddleInit,
DnaOrgNbr = newDpEligibleLoanSale.BranchOrgNbr.ToString()
});
}
else
{
appUser = await _userService.GetUserByAdEmailAsync(newDpEligibleLoanSale.EmployeeEmail);
}
if (appUser == null)
{
return activity.MapToActivity();
}
department = await _departmentRepository.FindUserHomeDepartment(appUser);
}
}
catch (Exception ex)
{
Log.Error(ex,
$"Failed to get data for NewDPEligibleLoanSaleActivity with sourceId: {activity.MapToActivity().SourceId}. Falling back to basic mapping.");
}
var mappedActivity = activity.MapToActivity();
mappedActivity.AppUserId = appUser?.Id ?? Guid.Empty;
mappedActivity.EmployeeId = appUser?.EmployeeId;
mappedActivity.DepartmentId = department?.Id ?? -1;
return mappedActivity;
}
public async Task<AppUser> SearchForUser(UserSearchRequest userSearchRequest)
{
var foundUsers = await _userService.GetAllUsersAsync(userSearchRequest);
if (foundUsers.Count < 1 && !string.IsNullOrWhiteSpace(userSearchRequest.MiddleInit))
{
foundUsers = await _userService.GetAllUsersAsync(new UserSearchRequest()
{
FirstName = userSearchRequest.FirstName,
LastName = userSearchRequest.LastName,
DnaOrgNbr = userSearchRequest.DnaOrgNbr
});
}
if (foundUsers.Count < 1)
{
foundUsers = await _userService.GetAllUsersAsync(new UserSearchRequest()
{
FirstName = userSearchRequest.FirstName,
LastName = userSearchRequest.LastName,
});
}
return foundUsers.Single();
}
}
@@ -0,0 +1,114 @@
using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer;
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
using LCU.API.Internal.Operations.IncentiveApiLib.Services;
using LCU.API.Internal.Operations.IncentiveApiLib.Repositories;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.API.Internal.Operations.IncentiveApi.Models.Constants;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity;
public class NewMortgageActivityStrategy : IExternalActivityStrategy
{
private readonly IUserService _userService;
private readonly IDepartmentRepository _departmentRepository;
public NewMortgageActivityStrategy(IUserService userService, IDepartmentRepository departmentRepository)
{
_userService = userService;
_departmentRepository = departmentRepository;
}
public bool CanMapActivity(IExternalActivity activity)
{
return
activity.GetActivityType() == ActivityType.Sale
&& (activity.GetProductType() == Product.Mortgage || activity.GetProductType() == Product.FirstMortgageReferral);
}
public async Task<Activity> MapToActivityManagerActivity(IExternalActivity activity)
{
AppUser appUser = null;
Department department = null;
try
{
if (activity is NewLoanSaleActivity newMortgageActivity)
{
if (IsFirstMortgageSale(newMortgageActivity))
{
appUser = await _userService.GetUserByAdEmailAsync(SystemAppUserConstants.FirstMortgageEmail);
}
else if (string.IsNullOrWhiteSpace(newMortgageActivity.EmployeeEmail))
{
appUser = await _userService.TryFindSingleUserByName(new UserNameSearchRequest()
{
FirstName = newMortgageActivity.AssociateFirst,
LastName = newMortgageActivity.AssociateLast,
MiddleInit = newMortgageActivity.AssociateMiddleInit,
DnaOrgNbr = newMortgageActivity.BranchOrgNbr.ToString()
});
}
else
{
appUser = await _userService.GetUserByAdEmailAsync(newMortgageActivity.EmployeeEmail);
}
if (appUser == null)
{
return activity.MapToActivity();
}
department = await _departmentRepository.FindUserHomeDepartment(appUser);
}
}
catch (Exception ex)
{
Log.Error(ex,
$"Failed to get data for NewMortgageSaleActivity with sourceId: {activity.MapToActivity().SourceId}. Falling back to basic mapping.");
}
var mappedActivity = activity.MapToActivity();
mappedActivity.AppUserId = appUser?.Id ?? Guid.Empty;
mappedActivity.EmployeeId = appUser?.EmployeeId;
mappedActivity.DepartmentId = department?.Id ?? -1;
return mappedActivity;
}
private bool IsFirstMortgageSale(NewLoanSaleActivity newMortgageActivity)
{
if (newMortgageActivity.GetProductType() == Product.FirstMortgageReferral
&& newMortgageActivity.GetActivityType() == ActivityType.Sale)
{
return true;
}
return false;
}
public async Task<AppUser> SearchForUser(UserSearchRequest userSearchRequest)
{
var foundUsers = await _userService.GetAllUsersAsync(userSearchRequest);
if (foundUsers.Count < 1 && !string.IsNullOrWhiteSpace(userSearchRequest.MiddleInit))
{
foundUsers = await _userService.GetAllUsersAsync(new UserSearchRequest()
{
FirstName = userSearchRequest.FirstName,
LastName = userSearchRequest.LastName,
DnaOrgNbr = userSearchRequest.DnaOrgNbr
});
}
if (foundUsers.Count < 1)
{
foundUsers = await _userService.GetAllUsersAsync(new UserSearchRequest()
{
FirstName = userSearchRequest.FirstName,
LastName = userSearchRequest.LastName,
});
}
return foundUsers.Single();
}
}
@@ -0,0 +1,77 @@
using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer;
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
using LCU.API.Internal.Operations.IncentiveApiLib.Services;
using LCU.API.Internal.Operations.IncentiveApiLib.Repositories;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.ExternalActivity;
public class NewVehicleActivityStrategy : IExternalActivityStrategy
{
private readonly IUserService _userService;
private readonly IDepartmentRepository _departmentRepository;
public NewVehicleActivityStrategy(IUserService userService, IDepartmentRepository departmentRepository)
{
_userService = userService;
_departmentRepository = departmentRepository;
}
public bool CanMapActivity(IExternalActivity activity)
{
return activity.GetActivityType() == ActivityType.Sale && activity.GetProductType() == Product.Vehicle;
}
public async Task<Activity> MapToActivityManagerActivity(IExternalActivity activity)
{
AppUser appUser = null;
Department department = null;
try
{
if (activity is NewLoanSaleActivity newVehicleActivity)
{
if (string.IsNullOrWhiteSpace(newVehicleActivity.EmployeeEmail))
{
try
{
appUser = await _userService.TryFindSingleUserByName(new UserNameSearchRequest()
{
FirstName = newVehicleActivity.AssociateFirst,
LastName = newVehicleActivity.AssociateLast,
MiddleInit = newVehicleActivity.AssociateMiddleInit,
DnaOrgNbr = newVehicleActivity.BranchOrgNbr.ToString()
});
}
catch (InvalidOperationException ioe)
{
Log.Error(ioe,
$"More than one user found for name query {newVehicleActivity.AssociateFirst}, {newVehicleActivity.AssociateLast}, will try employee email.");
}
}
else
{
appUser = await _userService.GetUserByAdEmailAsync(newVehicleActivity.EmployeeEmail);
}
if (appUser == null)
{
return activity.MapToActivity();
}
department = await _departmentRepository.FindUserHomeDepartment(appUser);
}
}
catch (Exception ex)
{
Log.Error(ex,
$"Failed to get data for NewVehicleActivityStrategy with sourceId: {activity.MapToActivity().SourceId}. Falling back to basic mapping.");
}
var mappedActivity = activity.MapToActivity();
mappedActivity.AppUserId = appUser?.Id ?? Guid.Empty;
mappedActivity.EmployeeId = appUser?.EmployeeId;
mappedActivity.DepartmentId = department?.Id ?? -1;
return mappedActivity;
}
}
@@ -0,0 +1,243 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.API.Internal.Operations.IncentiveApiLib.Repositories;
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.Library.Internal.IncentiveApiModels.Common;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public class GapInsuranceActivityService : IGapInsuranceActivityService
{
private readonly IActivityDetailService _activityDetailService;
private readonly IActivityRepository _activityRepository;
private readonly IActivityService _activityService;
private readonly IUnitOfWork _unitOfWork;
public GapInsuranceActivityService(
IActivityDetailService activityDetailService,
IActivityRepository activityRepository,
IActivityService activityService,
IUnitOfWork unitOfWork
)
{
_activityDetailService = activityDetailService;
_activityRepository = activityRepository;
_activityService = activityService;
_unitOfWork = unitOfWork;
}
public async Task<int> MatchOrRemoveGapAccessories(MonthRange monthRange = null)
{
var updateCount = 0;
monthRange ??= new MonthRange(DateTime.UtcNow.AddMonths(-3), DateTime.UtcNow);
var gapEligibleActivities = await _activityRepository.FindGapInsuranceEligible(monthRange);
if (!gapEligibleActivities.Any())
{
return updateCount;
}
var matchedGapEligibleActivities = gapEligibleActivities.Where(x => x.HasActiveGapAccessory()).ToList();
var matchedGapAccessoryActivities = await GetMatchingGapAccessoryActivities(matchedGapEligibleActivities);
matchedGapEligibleActivities = await ValidateMatchedActivityAccessories(matchedGapEligibleActivities, matchedGapAccessoryActivities);
var unmatchedGapEligibleActivities = gapEligibleActivities.Except(matchedGapEligibleActivities).ToList();
var gapActivities = await _activityRepository.GetFilteredActivities(monthRange, Product.GAPInsurance, ActivityState.Accepted);
foreach (var activity in unmatchedGapEligibleActivities)
{
try
{
updateCount += await TryMatchGapEligibleActivityWithAccessory(activity, gapActivities) != null ? 2 : 0;
}
catch (Exception error)
{
Log.Error(error, $"MatchOrRemoveGapAccessories failed processing Activity Id: {activity.Id}");
}
}
return updateCount;
}
private async Task<Activity?> TryMatchGapEligibleActivityWithAccessory(Activity gapEligibleActivity, List<Activity> gapActivities)
{
if (gapActivities.Count <= 0)
{
return null;
}
var matchingGapAccessories = gapActivities
.Where(x => x.Detail.AccountNumber.Equals(gapEligibleActivity.Detail.AccountNumber, StringComparison.CurrentCultureIgnoreCase))
.ToList();
if (matchingGapAccessories.Count <= 0)
{
return null;
}
if (matchingGapAccessories.Count > 1)
{
throw new Exception(
$"Multiple matching GAP Accessory Activities found for Activity Id: {gapEligibleActivity.Id} using AccountNumber: {gapEligibleActivity.Detail.AccountNumber}");
}
var matchedAccessoryActivity = matchingGapAccessories.First();
return await MatchGapAccessory(gapEligibleActivity, matchedAccessoryActivity);
}
private async Task<Activity> MatchGapAccessory(Activity parentActivity, Activity accessoryActivity)
{
var addedDateTime = accessoryActivity.Accepted.Date.AddHours(12);
await ApplyMatchedChangeUpdates(parentActivity, accessoryActivity, addedDateTime);
await ApplyMatchedChangeUpdates(accessoryActivity, parentActivity, addedDateTime);
await _unitOfWork.CompleteAsync();
return parentActivity;
}
private async Task ApplyMatchedChangeUpdates(Activity targetActivity, Activity linkedActivity, DateTime addedDateTime)
{
targetActivity.Detail.GapInsuranceAddDate = addedDateTime;
targetActivity.MatchingGapInsuranceActivityId = linkedActivity.Id;
await _activityDetailService.SaveActivityDetailUpdate(new ActivityDetailUpdate
{
ActivityDetailId = targetActivity.Detail.Id,
Note = $"GapInsuranceAddDate has changed from NULL to {addedDateTime}"
});
if (targetActivity.Detail.GapInsuranceRemoveDate != null)
{
targetActivity.Detail.GapInsuranceRemoveDate = null;
await _activityDetailService.SaveActivityDetailUpdate(new ActivityDetailUpdate
{
ActivityDetailId = targetActivity.Detail.Id,
Note = $"GapInsuranceRemoveDate has changed from {targetActivity.Detail.GapInsuranceRemoveDate} to NULL"
});
}
await _activityService.ApplyStateChange(new ActivityStateChange
{
ActivityId = targetActivity.Id,
ActivityMatchId = linkedActivity.Id,
Note = $"Matched {linkedActivity.Product} Activity Id: {linkedActivity.Id} Added",
State = targetActivity.State
}, false);
}
private async Task<List<Activity>> ValidateMatchedActivityAccessories(List<Activity> matchedActivities, List<Activity> accessoryActivities)
{
var accessoryRemovedActivities = new List<Activity>();
foreach (var parentActivity in matchedActivities)
{
try
{
var accessoryActivity = accessoryActivities.FirstOrDefault(x => x.MatchingGapInsuranceActivityId == parentActivity.Id) == null
? await _activityRepository.GetActivityById(parentActivity.MatchingGapInsuranceActivityId)
: accessoryActivities.FirstOrDefault(x => x.MatchingGapInsuranceActivityId == parentActivity.Id);
if (accessoryActivity == null && parentActivity.Detail.GapInsuranceRemoveDate == null)
{
accessoryRemovedActivities.Add(await RemoveMissingGapAccessory(parentActivity));
continue;
}
if (parentActivity?.State == ActivityState.Accepted && accessoryActivity?.State == ActivityState.Accepted)
{
continue;
}
accessoryRemovedActivities.Add(await RemoveGapAccessory(parentActivity, accessoryActivity));
}
catch (Exception ex)
{
var parentActivityId = parentActivity != null ? parentActivity.Id.ToString() : "NULL";
Log.Error(ex, $"Failed to validate matched GAP accessory for Activity Id:{parentActivityId}");
}
}
Log.Information($"Removed GAP Accessory From {accessoryRemovedActivities.Count} Activities");
return matchedActivities.Except(accessoryRemovedActivities).ToList();
}
private async Task<Activity> RemoveGapAccessory(Activity parentActivity, Activity accessoryActivity)
{
var removedDate = await DetermineRemovedDate(parentActivity, accessoryActivity);
await ApplyRemovalChangeUpdates(parentActivity, accessoryActivity, removedDate);
await ApplyRemovalChangeUpdates(accessoryActivity, parentActivity, removedDate);
await _unitOfWork.CompleteAsync();
return parentActivity.State == ActivityState.Accepted ? parentActivity : null;
}
private async Task ApplyRemovalChangeUpdates(Activity targetActivity, Activity linkedActivity, DateTime removedDateTime)
{
targetActivity.Detail.GapInsuranceRemoveDate = removedDateTime;
targetActivity.MatchingGapInsuranceActivityId = 0;
await _activityDetailService.SaveActivityDetailUpdate(new ActivityDetailUpdate
{
ActivityDetailId = targetActivity.Detail.Id,
Note = $"GapInsuranceRemoveDate has changed from NULL to {removedDateTime}"
});
await _activityService.ApplyStateChange(new ActivityStateChange
{
ActivityId = targetActivity.Id,
ActivityMatchId = linkedActivity.Id,
Note = $"Matched {linkedActivity.Product} Activity Id: {linkedActivity.Id} Removed",
State = targetActivity.State
}, false);
}
private static async Task<DateTime> DetermineRemovedDate(Activity parentActivity, Activity accessoryActivity)
{
if (parentActivity.State == ActivityState.Accepted && accessoryActivity.State != ActivityState.Accepted)
{
return await Task.FromResult(accessoryActivity.Accepted.Date.AddHours(12));
}
if (parentActivity.State != ActivityState.Accepted && accessoryActivity.State == ActivityState.Accepted)
{
return await Task.FromResult(parentActivity.Accepted.Date.AddHours(12));
}
return await Task.FromResult(parentActivity.LastUpdated <= accessoryActivity.LastUpdated
? parentActivity.Accepted.Date.AddHours(12)
: accessoryActivity.Accepted.Date.AddHours(12));
}
private async Task<Activity?> RemoveMissingGapAccessory(Activity activity)
{
Log.Warning($"Unable To Find GAP Accessory Activity Id: {activity.MatchingGapInsuranceActivityId}, Removing GAP Accessory From Activity Id: {activity.Id}");
var removeDate = DateTime.Now.Date.AddHours(12);
activity.MatchingGapInsuranceActivityId = 0;
activity.Detail.GapInsuranceRemoveDate = removeDate;
await _activityDetailService.SaveActivityDetailUpdate(new ActivityDetailUpdate
{
ActivityDetailId = activity.Detail.Id,
Note = $"GapInsuranceRemoveDate has changed from NULL to {removeDate}"
});
var activityStateChange = new ActivityStateChange
{
ActivityId = activity.Id,
Note = $"{Product.GAPInsurance} Accessory Activity Removed, Accessory Activity Not Found",
State = activity.State
};
await _activityService.ApplyStateChange(activityStateChange);
return activity.State == ActivityState.Accepted ? activity : null;
}
private async Task<List<Activity>> GetMatchingGapAccessoryActivities(IReadOnlyCollection<Activity> matchedActivities)
{
if (!matchedActivities.Any())
{
return new List<Activity>();
}
var ids = matchedActivities
.Select(activity => activity.MatchingGapInsuranceActivityId)
.ToList();
return await _activityRepository.FindById(ids);
}
}
@@ -0,0 +1,215 @@
using LCU.API.Internal.Operations.IncentiveApi.Services.DispersalStrategy;
using LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal;
using LCU.API.Internal.Operations.IncentiveApiLib.Services;
using LCU.API.Internal.Operations.IncentiveApiLib.Repositories;
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices;
using LCU.Library.Internal.ApiResponse.Services;
using LCU.Library.Internal.Exceptions;
using LCU.Library.Internal.IncentiveApiModels.Results;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public class GoalDispersalService : IGoalDispersalService
{
private readonly IUserService _userService;
private readonly IGoalService _goalService;
private readonly IDepartmentRepository _departmentRepository;
private readonly INotificationApiService _notificationService;
private readonly IDispersalStrategyService _dispersalStrategyService;
private readonly ProcessResult _goalDispersalProcessResult;
public GoalDispersalService(
IUserService userService,
IGoalService goalService,
IDepartmentRepository departmentRepository,
INotificationApiService notificationService,
IDispersalStrategyService dispersalStrategyService
)
{
_userService = userService;
_goalService = goalService;
_departmentRepository = departmentRepository;
_notificationService = notificationService;
_dispersalStrategyService = dispersalStrategyService;
_goalDispersalProcessResult = new ProcessResult();
}
public async Task<GoalDispersalReceipt> DisperseGoalsByExtrapolatingLastMonth(int departmentId, int dispersalMonth, int dispersalYear)
{
try
{
var lastMonth = dispersalMonth == 1 ? 12 : dispersalMonth - 1;
var lastMonthCorrectedYear = dispersalMonth == 1 ? dispersalYear - 1 : dispersalYear;
var dispersalMonthStartDateTime = DateTime.Parse($"{dispersalYear}-{dispersalMonth}-01");
var departmentGoals = await _goalService.GetAnnualDepartmentGoalsAsync(departmentId, dispersalYear);
var dispersalMonthDepartmentGoals = departmentGoals.MonthlyGoals[dispersalMonth - 1];
var dispersalMonthPersonnel = await _userService.FindDepartmentAssignments(
new DepartmentPersonnelSearchRequest()
{
DepartmentId = departmentId,
Month = dispersalMonth,
Year = dispersalYear,
IncludeAllPersonnel = true
}
);
var dispersalMonthPersonnelGoals = await _goalService.GetMonthlyPersonnelGoalsAsync(
new DepartmentPersonnelSearchRequest()
{
DepartmentId = departmentId,
Month = dispersalMonth,
Year = dispersalYear,
IncludeAllPersonnel = true
}
);
var modelMonthPersonnelGoals = await _goalService.GetMonthlyPersonnelGoalsAsync(
new DepartmentPersonnelSearchRequest()
{
DepartmentId = departmentId,
Month = lastMonth,
Year = lastMonthCorrectedYear,
IncludeAllPersonnel = true
}
);
if (modelMonthPersonnelGoals.GoalAudit.All(x => x.Value < 1))
{
var weightedDispersalRequest = new DefaultWeightedGoalDispersalData()
{
DepartmentId = departmentId,
DispersalMonthStartDate = dispersalMonthStartDateTime,
DispersalMonthPersonnel = dispersalMonthPersonnel,
DispersalMonthDepartmentGoals = dispersalMonthDepartmentGoals
};
var weightedReceipt = _dispersalStrategyService.DefaultWeightedDispersal(weightedDispersalRequest);
await _goalService.SaveGoalRangeAsync(weightedReceipt.GoalChanges);
return weightedReceipt;
}
departmentGoals = await _goalService.GetAnnualDepartmentGoalsAsync(departmentId, lastMonthCorrectedYear);
var lastMonthDepartmentGoals = departmentGoals.MonthlyGoals[lastMonth - 1];
var extrapolateGoalsDispersalRequest = new ExtrapolationGoalDispersalData()
{
DepartmentId = departmentId,
DispersalMonthStartDate = dispersalMonthStartDateTime,
DispersalMonthDepartmentGoals = dispersalMonthDepartmentGoals,
DispersalMonthPersonnelGoals = dispersalMonthPersonnelGoals,
ModelDepartmentGoals = lastMonthDepartmentGoals,
ModelPersonnelGoals = modelMonthPersonnelGoals,
DispersalMonthPersonnel = dispersalMonthPersonnel
};
var receipt = _dispersalStrategyService.ExtrapolateFromExistingGoals(extrapolateGoalsDispersalRequest);
await _goalService.SaveGoalRangeAsync(receipt.GoalChanges);
return receipt;
}
catch (Exception ex)
{
Log.Error(ex, $"Failed to DisperseGoalsForNextMonth for dept:{departmentId} month:{dispersalMonth} dispersalYear: {dispersalYear}");
throw new Exceptions.ServerErrorException();
}
}
public async Task<Dictionary<string, GoalDispersalReceipt>> DisperseAllDepartmentGoalsForMonth(int month, int year)
{
var results = new Dictionary<string, GoalDispersalReceipt>();
var departments = await _departmentRepository.GetAllDepartmentsAsync();
foreach (var department in departments)
{
try
{
if (department.Id == null)
{
throw new Exception($"Department Has No Id. Skipping Dispersal: {department.DepartmentName}");
}
var departmentKey = $"{department.Id} - {department.DepartmentName}";
Log.Debug($"Dispersing Goals for Department: {departmentKey}");
var receipt = await DisperseGoalsByExtrapolatingLastMonth((int)department.Id, month, year);
results.Add(departmentKey, receipt);
Log.Debug($"Completed Dispersal for: {departmentKey}");
}
catch (Exception ex)
{
var departmentKey = $"{department.Id} - {department.DepartmentName}";
Log.Error(ex, $"Failed Month Start Auto-Disperse of Personnel Goals for Department: {departmentKey}");
results.Add(departmentKey, null);
}
}
return results;
}
public async Task ExecuteMonthStartDispersal(GoalDispersalRequest request)
{
try
{
Log.Information("Executing Monthly Goal Dispersal");
var result = await DisperseAllDepartmentGoalsForMonth(request.StartDate.Month, request.StartDate.Year);
Log.Information("Finished Monthly Goal Dispersal. Sending Report Email.");
await _notificationService.SendMonthStartGoalDispersalReceipt(result, request.GoalDispersalNotification);
_goalDispersalProcessResult.MessageStack.Add($"Goal Dispersal Service Processed for {request.StartDate.Month}{request.StartDate.Year} records.");
Log.Information("Successfully Sent Report Email.");
await QueueReturnResponse.QueueResponse(request, _goalDispersalProcessResult.Success, _goalDispersalProcessResult.MessageStack);
}
catch (Exception exception)
{
Log.Fatal(exception, "There was an Unhandled error Executing Month Start Dispersal");
var message = $"There was an issue with handling the Goal Dispersal Service Process {request.StartDate.Month}{request.StartDate.Year}. Exception Message: {exception.Message}.";
_goalDispersalProcessResult.MessageStack.Add(message);
_goalDispersalProcessResult.Success = false;
await QueueReturnResponse.QueueResponse(request, _goalDispersalProcessResult.Success, _goalDispersalProcessResult.MessageStack);
throw new ServiceException(message);
}
}
public async Task<GoalDispersalReceipt> DepartmentGoalChangeReDispersal(DepartmentGoalExtrapolationRequest request)
{
var personnelGoals = await _goalService.GetMonthlyPersonnelGoalsAsync(new DepartmentPersonnelSearchRequest()
{
DepartmentId = request.DepartmentId,
IncludeAllPersonnel = true,
Year = request.DepartmentGoalStartDate.Year,
Month = request.DepartmentGoalStartDate.Month,
});
var receipt = _dispersalStrategyService.ExtrapolateSingleDepartmentProduct(request, personnelGoals);
return receipt;
}
public async Task<GoalDispersalReceipt> CustomWeightedDispersal(CustomWeightedDispersalRequest request)
{
try
{
var departmentGoals = await _goalService.GetAnnualDepartmentGoalsAsync(request.DepartmentId, request.year);
var productDepartmentGoal = departmentGoals.MonthlyGoals[request.month - 1].ProductGoals[request.DispersalProduct];
var departmentPersonnel = await _userService.FindDepartmentAssignments(
new DepartmentPersonnelSearchRequest()
{
DepartmentId = request.DepartmentId,
Month = request.month,
Year = request.year,
IncludeAllPersonnel = true
}
);
var receipt = _dispersalStrategyService.CustomWeightedDispersal(request, departmentPersonnel, productDepartmentGoal);
await _goalService.SaveGoalRangeAsync(receipt.GoalChanges);
return receipt;
}
catch (Exception ex)
{
Log.Error(ex, "Custom Weighted Dispersal Failed");
throw new Exceptions.ServerErrorException();
}
}
}
@@ -0,0 +1,254 @@
using LCU.API.Internal.Operations.IncentiveApi.Extensions;
using LCU.API.Internal.Operations.IncentiveApi.Services.DispersalStrategy;
using LCU.API.Internal.Operations.IncentiveApi.Models;
using LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal;
using LCU.API.Internal.Operations.IncentiveApiLib.Services;
using LCU.API.Internal.Operations.IncentiveApiLib.Repositories;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Common;
using LCU.API.Internal.Operations.IncentiveApiLib.Persistence;
using LCU.Library.Internal.IncentiveApiModels.Requests;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public class GoalService : IGoalService
{
private readonly IGoalRepository _goalRepository;
private readonly IGoalChangeRepository _goalChangeRepository;
private readonly ICurrentUserService _currentUserService;
private readonly IDispersalStrategyService _dispersalStrategyService;
private readonly IDepartmentRepository _departmentRepository;
private readonly IUserService _userService;
private readonly IUnitOfWork _unitOfWork;
public GoalService(
IGoalRepository goalRepository,
IGoalChangeRepository goalChangeRepository,
ICurrentUserService currentUserService,
IDispersalStrategyService dispersalStrategyService,
IUnitOfWork unitOfWork,
IUserService userService,
IDepartmentRepository departmentRepository
)
{
_goalRepository = goalRepository;
_goalChangeRepository = goalChangeRepository;
_currentUserService = currentUserService;
_unitOfWork = unitOfWork;
_userService = userService;
_departmentRepository = departmentRepository;
_dispersalStrategyService = dispersalStrategyService;
}
public async Task<List<Goal>> SaveGoalAsync(GoalChange goalChange)
{
return await SaveGoalRangeAsync(new List<GoalChange>() { goalChange });
}
private async Task<GoalChange> ConfirmAppUserIdAndSaveGoalChange(GoalChange goalChange)
{
if (!goalChange.IsDepartmentGoalChange())
{
var appUser = await _userService.GetUserByEmployeeIdAsync(goalChange.PersonId);
goalChange.AppUserId = appUser.Id;
}
await _goalChangeRepository.SaveGoalChangeAsync(goalChange);
return goalChange;
}
public async Task<List<Goal>> SaveGoalRangeAsync(List<GoalChange> goalChanges)
{
var goalsToSave = new List<Goal>();
foreach (var goalChange in goalChanges)
{
await ConfirmAppUserIdAndSaveGoalChange(goalChange);
var goal = await _goalRepository.FindExistingGoal(goalChange);
if (goal != null)
{
var originalGoalValue = goal.Value;
goal.Value = goalChange.Value;
if (goal.IsDepartmentGoal() && originalGoalValue != goal.Value)
{
goalsToSave.AddRange(await CreatePersonnelGoalChangesFromDepartmentGoalChange(goalChange, originalGoalValue));
}
}
else
{
goal = new Goal()
{
AppUserId = goalChange.AppUserId,
Product = goalChange.Product,
EmployeeId = goalChange.PersonId,
DepartmentId = goalChange.DepartmentId,
StartDate = goalChange.StartDate.StartOfMonth(),
EndDate = goalChange.EndDate.EndOfMonth(),
Value = goalChange.Value
};
await _goalRepository.SaveGoalAsync(goal);
}
goalsToSave.Add(goal);
}
await _unitOfWork.CompleteAsync();
return goalsToSave;
}
private async Task<List<Goal>> CreatePersonnelGoalChangesFromDepartmentGoalChange(GoalChange goalChange, int originalGoalValue)
{
if (!goalChange.IsDepartmentGoalChange()) return new List<Goal>();
var personnelGoals = new List<Goal>();
var departmentPersonnelGoals = await GetMonthlyPersonnelGoalsAsync(
new DepartmentPersonnelSearchRequest()
{
DepartmentId = goalChange.DepartmentId,
IncludeAllPersonnel = true,
Year = goalChange.StartDate.Year,
Month = goalChange.StartDate.Month,
});
var receipt = _dispersalStrategyService.ExtrapolateSingleDepartmentProduct(
new DepartmentGoalExtrapolationRequest()
{
DepartmentGoalEndDate = goalChange.EndDate.EndOfMonth(),
DepartmentGoalStartDate = goalChange.StartDate.StartOfMonth(),
DepartmentId = goalChange.DepartmentId,
NewDepartmentGoal = goalChange.Value,
OriginalDepartmentGoal = originalGoalValue,
Product = goalChange.Product
}, departmentPersonnelGoals);
foreach (var reDispersedGoalChange in receipt.GoalChanges)
{
await ConfirmAppUserIdAndSaveGoalChange(reDispersedGoalChange);
var reDispersedGoal = await CreateOrUpdateGoalFromGoalChange(reDispersedGoalChange);
personnelGoals.Add(reDispersedGoal);
}
return personnelGoals;
}
private async Task<Goal> CreateOrUpdateGoalFromGoalChange(GoalChange goalChange)
{
var goal = await _goalRepository.FindExistingGoal(goalChange);
if (goal != null)
{
goal.Value = goalChange.Value;
}
else
{
goal = new Goal()
{
Product = goalChange.Product,
AppUserId = goalChange.AppUserId,
EmployeeId = goalChange.PersonId,
DepartmentId = goalChange.DepartmentId,
StartDate = goalChange.StartDate.StartOfMonth(),
EndDate = goalChange.EndDate.EndOfMonth(),
Value = goalChange.Value
};
await _goalRepository.SaveGoalAsync(goal);
}
return goal;
}
public async Task<DepartmentGoals> GetAnnualDepartmentGoalsAsync(int departmentId, int year)
{
var goals = new DepartmentGoals(departmentId, year);
goals.ApplyAll(await _goalRepository.FindAnnualDepartmentGoals(departmentId, year));
return goals;
}
public async Task<IReadOnlyCollection<DepartmentGoals>> GetAllAnnualDepartmentGoalsAsync(int year)
{
var departments = await _departmentRepository.GetAllDepartmentsAsync();
var departmentGoals = new List<DepartmentGoals>();
foreach (var department in departments)
{
if (department.Id != null)
{
departmentGoals.Add(await GetAnnualDepartmentGoalsAsync(department.Id.Value, year));
}
}
return departmentGoals;
}
public async Task<PersonnelGoals> GetMonthlyPersonnelGoalsAsync(DepartmentPersonnelSearchRequest searchRequest)
{
var users = await _userService.FindDepartmentAssignments(searchRequest);
var goals = new PersonnelGoals(searchRequest.DepartmentId, searchRequest.Year, searchRequest.Month, users);
goals.ApplyAll(await _goalRepository.FindMonthlyPersonnelGoals(searchRequest.DepartmentId, users,
searchRequest.Year, searchRequest.Month));
return goals;
}
public async Task<IReadOnlyCollection<Goal>> GetGoalsAsync(GoalSearchRequest goalSearchRequest)
{
return await _goalRepository.GetGoalsAsync(goalSearchRequest);
}
public async Task<IReadOnlyCollection<GoalChange>> GetGoalChangesAsync()
{
return await _goalChangeRepository.GetGoalChangesAsync();
}
public async Task<EmployeeGoals> GetEmployeeGoalsForMonthRangeAsync(AppUser employee, MonthRange monthRange)
{
var users = new List<AppUser>()
{
employee
};
var goals = new EmployeeGoals(employee, monthRange);
foreach (var dept in goals.Departments)
{
goals.ApplyAll(
await _goalRepository.FindPersonnelGoalsByMonthRange(dept.DepartmentId, users, monthRange));
}
return goals;
}
public async Task<IReadOnlyCollection<DepartmentGoalsForRange>> GetEmployeeDepartmentsGoalsForMonthRangeAsync(AppUser employee, MonthRange monthRange)
{
var allGoals = new List<DepartmentGoalsForRange>();
foreach (var dept in employee.Departments)
{
var isAssignedInMonthRange = dept.StartDate.Date < monthRange.EndTime.Date &&
(dept.EndDate == null ||
monthRange.StartTime.Date < dept.EndDate.Value.Date);
if (isAssignedInMonthRange)
{
var StartDate = dept.StartDate.Date < monthRange.StartTime.Date
? monthRange.StartTime.Date
: dept.StartDate.Date;
var EndDate = dept.EndDate == null ? monthRange.EndTime.Date
: dept.EndDate.Value.Date > monthRange.EndTime.Date ? monthRange.EndTime.Date
: dept.EndDate.Value.Date;
var monthRangeOfAssignment = new MonthRange
(
StartDate.Year,
StartDate.Month,
EndDate.Year,
EndDate.Month
);
var goals = new DepartmentGoalsForRange(dept.DepartmentId, monthRangeOfAssignment);
var deptGoals =
await _goalRepository.FindDepartmentGoalsByMonthRange(goals.DepartmentId, goals.MonthRange);
goals.ApplyAll(deptGoals);
allGoals.Add(goals);
}
}
return allGoals;
}
}
@@ -0,0 +1,13 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public interface IActivityDetailService
{
Task<List<Activity>> TryUpdateActivityAccountDetails(List<Activity> activities);
Task<List<Activity>> TryUpdateLicReferralStationDetail(List<Activity> activities);
Task<List<Activity>> TryUpdateDpEligibleLoanDetail(List<Activity> activities);
Task<Activity> TryUpdateDpAccessory(Activity saleActivity, Activity dpAccessoryActivity);
Task<List<ActivityDetailUpdate>> GetAllActivityDetailUpdates(int activityDetailId);
Task<ActivityDetailUpdate> SaveActivityDetailUpdate(ActivityDetailUpdate activityDetailUpdate);
}
@@ -0,0 +1,34 @@
using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.API.Internal.Operations.IncentiveApi.Models;
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
using LCU.Library.Internal.IncentiveApiModels.Common;
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.Library.Internal.IncentiveApiModels.Results;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public interface IActivityService
{
Task<int> SaveActivityAsync(Activity activity);
Task SaveActivityRangeAsync(IReadOnlyCollection<Activity> activity);
Task<IReadOnlyCollection<Activity>> GetActivitiesAsync();
Task<PagedResult<Activity>> ListByFilterAsync(ActivityFilter filter);
Task<Activity> ApplyStateChange(ActivityStateChange activityStateChange, bool commitTransaction = true);
Task<IReadOnlyCollection<ActivityStateChange>> GetActivityStateChanges(int activityId);
Task<IReadOnlyCollection<DepartmentProgress>> GetDepartmentActivityForMonthRangeAsync(List<UserDepartment> userDepartments, MonthRange monthRange);
Task<Activity> GetActivity(int activityId);
Task<Activity> CreateAndRetrieveActivity(Activity activity);
Task<Activity> GetActivityForStateUpdate(Activity queryActivity);
Task<Activity> GetActivityForDetailUpdate(Activity queryActivity);
Task ValidateCanApplyActivityStateChange(AppUser appUser, ActivityStateChange activityStateChange);
Task<ActivityTransferAction> RequestTransfer(ActivityTransferRequest activityTransferRequest);
Task<ActivityTransferAction> TransferToOwnDepartment(ActivityTransferRequest activityTransferRequest, AppUser employee);
Task<IReadOnlyCollection<ActivityStateChange>> MatchOrExpireReferrals();
Task<ProcessResult> RunReferralStatusUpdates();
Task<ActivityTransferAction> ApproveTransfer(AppUser appUser, ActivityTransferApprovalRequest activityTransferApproval);
Task<ActivityTransferAction> GetRequestedTransfer(int activityId);
Task CancelPendingTransfersBetween(MonthRange range);
Task<List<IExternalActivity>> FilterExternalActivitiesByExistingSourceId(List<IExternalActivity> incomingEternalActivities);
Task<AuthorizedActivityCommands> GetActivityAuthorizedCommands(AppUser appUser, int activityId);
}
@@ -0,0 +1,13 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public interface IAuthorizationService
{
Task<bool> AppUserCanMakeStateChange(AppUser appUser, Activity activity, ActivityState targetState, int targetDepartmentId = 0);
Task<bool> AppUserCanViewReport(AppUser appUser, string permissionCode);
Task<bool> ValidateHierarchicalDepartmentPermission(AppUser appUser, IReadOnlyCollection<Role> allowedDirectRoles, int departmentId);
Task<bool> ValidateIsBusinessLineManager(AppUser appUser, int departmentId);
Task<bool> ValidateIsRegionalManager(AppUser appUser, int departmentId);
}
@@ -0,0 +1,9 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public interface ICommissionService
{
Task<IReadOnlyCollection<Commission>> GetCommissionsAsync();
Task SaveCommissionsAsync(List<Commission> commissions);
}
@@ -0,0 +1,14 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.API.Internal.Operations.IncentiveApi.Models;
using LCU.Library.Internal.IncentiveApiModels.Common;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public interface IDashboardService
{
Task<RegionProgress> GetRegionProgressForMonthRangeAsync(Region region, MonthRange monthRange);
Task<BusinessLineProgress> GetBusinessLineProgressForMonthRangeAsync(BusinessLine bl, MonthRange monthRange);
Task<EmployeeProgress> GetEmployeeProgressForMonthRangeAsync(AppUser employee, MonthRange monthRange);
Task<IReadOnlyCollection<DepartmentProgress>> GetDepartmentProgressForMonthRangeAsync(List<UserDepartment> userDepartments, MonthRange monthRange);
Task<DepartmentProgress> GetDepartmentProgressForMonthRangeAsync(int departmentId, MonthRange monthRange);
}
@@ -0,0 +1,13 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Common;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public interface IDeadActivityService
{
public Task<DeadActivity> SaveDeadActivity(DeadActivity newDeadActivity);
Task<List<DeadActivity>> SaveDeadActivityRange(List<DeadActivity> newDeadActivities);
public Task<DeadActivity> GetDeadActivity(int activityId);
public Task<PagedResult<DeadActivity>> ListByFilterAsync(DeadActivityFilter filter);
Task<DeadActivity> ProcessDeadActivity(int activityId, int activityRef);
}
@@ -0,0 +1,19 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.API.Internal.Operations.IncentiveApi.Models;
using LCU.Library.Internal.IncentiveApiModels.Common;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public interface IDepartmentService
{
Task<IReadOnlyCollection<Department>> GetAllDepartmentsAsync();
Task<Department> GetDepartmentByIdAsync(int id);
Task<Department> SaveDepartmentAsync(Department department);
Task<Department> UpdateDepartmentAsync(Department department, int id);
Task<IReadOnlyCollection<Department>> GetDepartmentsByRegionAsync(Region region);
Task<IReadOnlyCollection<Department>> GetDepartmentsByBusinessLineAsync(BusinessLine businessLine);
Task<DepartmentDetails> GetDepartmentDetailsAsync(int id);
Task<IReadOnlyCollection<Department>> GetHierarchicalDepartmentsForEmployeeAsync(AppUser employee, bool includeManagers);
Task<Department> GetDepartmentsByDayforceGlCodeAsync(int glCode);
Task<IReadOnlyCollection<UserDepartment>> FindDepartmentAssignmentsForRange(int departmentId, MonthRange monthRange);
}
@@ -0,0 +1,10 @@
using LCU.Library.Internal.IncentiveApiModels.Requests;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public interface IDnaActivitySyncService
{
[Obsolete("01/01/2025 - Removed and located in each activity type service.")]
Task SyncLatestActivitiesFromDna();
Task HandleProcessRequest(IncentiveProcessRequest request);
}
@@ -0,0 +1,13 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Accounts;
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Transaction;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public interface IDnaService
{
Task<List<ActivityAccountDetails>> GetNewAccountDetailsForActivities(List<Activity> accountNbrs);
Task<List<NewActivityManagerReferral>> QueryActivityManagerForActivities(List<string> activityManagerIds);
Task<List<TransactionHistory>> GetTransactionHistory(string accountNumber, DateTime startDate, DateTime endDate, List<string> transactionCodes);
}
@@ -0,0 +1,8 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public interface IEStatementService
{
Task<IReadOnlyCollection<ActivityStateChange>> UpdateOrExpireEStatementReferral();
}
@@ -0,0 +1,8 @@
using LCU.Library.Internal.IncentiveApiModels.Common;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public interface IGapInsuranceActivityService
{
Task<int> MatchOrRemoveGapAccessories(MonthRange monthRange = null);
}
@@ -0,0 +1,13 @@
using LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal;
using LCU.Library.Internal.IncentiveApiModels.Requests;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public interface IGoalDispersalService
{
Task<Dictionary<string, GoalDispersalReceipt>> DisperseAllDepartmentGoalsForMonth(int month, int year);
Task<GoalDispersalReceipt> DisperseGoalsByExtrapolatingLastMonth(int departmentId, int dispersalMonth, int dispersalYear);
Task ExecuteMonthStartDispersal(GoalDispersalRequest request);
Task<GoalDispersalReceipt> DepartmentGoalChangeReDispersal(DepartmentGoalExtrapolationRequest request);
Task<GoalDispersalReceipt> CustomWeightedDispersal(CustomWeightedDispersalRequest request);
}
@@ -0,0 +1,19 @@
using LCU.API.Internal.Operations.IncentiveApi.Models;
using LCU.Library.Internal.IncentiveApiModels.Common;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Requests;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public interface IGoalService
{
Task<List<Goal>> SaveGoalAsync(GoalChange goalChange);
Task<DepartmentGoals> GetAnnualDepartmentGoalsAsync(int departmentId, int year);
Task<IReadOnlyCollection<DepartmentGoals>> GetAllAnnualDepartmentGoalsAsync( int year);
Task<PersonnelGoals> GetMonthlyPersonnelGoalsAsync(DepartmentPersonnelSearchRequest searchRequest);
Task<IReadOnlyCollection<Goal>> GetGoalsAsync(GoalSearchRequest goalSearchRequest);
Task<IReadOnlyCollection<GoalChange>> GetGoalChangesAsync();
Task<List<Goal>> SaveGoalRangeAsync(List<GoalChange> goalChanges);
Task<EmployeeGoals> GetEmployeeGoalsForMonthRangeAsync(AppUser employee, MonthRange range);
Task<IReadOnlyCollection<DepartmentGoalsForRange>> GetEmployeeDepartmentsGoalsForMonthRangeAsync(AppUser employee, MonthRange monthRange);
}
@@ -0,0 +1,10 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public interface IProcessorHistoryService
{
public Task<ProcessorHistoryRecord> GetProcessorHistory(Process process);
public Task<Process> SaveProcessorHistory(ProcessorHistoryRecord historyRecord);
}
@@ -0,0 +1,14 @@
using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.API.Internal.Operations.IncentiveApi.Models;
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
public interface ISettingsService
{
Task<RoleMapping> SaveRoleMappingAsync(RoleMappingUpdateRequest roleMapping);
Task SaveRoleMappingRangeAsync(IEnumerable<RoleMapping> roleMapping);
Task SaveNewUnknownRoleMappingRangeAsync(IEnumerable<string> unknownRoles);
Task<IEnumerable<RoleMapping>> GetRoleMappingsAsync();
Task<List<ReportSettings>> GetAuthorizedReportSettings(AppUser appUser);
}
@@ -0,0 +1,14 @@
using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
namespace LCU.API.Internal.Operations.IncentiveApi.Services
{
public interface IUserAssignmentService
{
Task<ICollection<UserAssignment>> GetAllUserAssignmentsAsync();
Task<UserAssignment> GetUserAssignment(AssignmentType assignmentType, string key);
Task<UserAssignment> SetRegionalManager(UserAssignmentRequest userAssignmentRequest);
}
}
@@ -0,0 +1,36 @@
using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer;
using LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce;
using LCU.Library.Internal.IncentiveApiModels.Common;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.Library.Internal.IncentiveApiModels.Results;
namespace LCU.API.Internal.Operations.IncentiveApiLib.Services;
public interface IUserService
{
Task<ICollection<AppUser>> GetAllUsersAsync(UserSearchRequest userSearchRequest);
Task<AppUser> GetUserByEmployeeIdAsync(string employeeId);
Task<AppUser> GetUserByAdEmailAsync(string adEmail);
Task<AppUser> GetActiveUserByEmployeeIdAsync(string employeeId);
Task<AppUser> GetActiveUserByAdEmailAsync(string adEmail);
Task<AppUser> FindUserAsync( string employeeId, string adEmail);
Task<AppUser> SaveUserAsync(AppUser newAppUser);
Task<ICollection<AppUser>> SaveUserRangeAsync(List<AppUser> newUsers);
Task<AppUser> SyncDayForceEmployee(ActiveEmployeeResult activeEmployee);
Task<AppUser> TerminateDayForceEmployee(TerminatedEmployeeResult terminatedEmployee);
Task<List<UserDepartment>> AssignDepartments(string employeeId, DepartmentAssignmentRequest assignment);
Task<UserDepartment> UpdateDepartmentAssignment(DepartmentUpdateRequest request);
Task<List<AppUser>> FindDepartmentAssignments(DepartmentPersonnelSearchRequest searchRequest);
Task<List<Permission>> AssignPermissions(string employeeId, List<string> permCodes);
Task<List<Permission>> RemovePermissions(string employeeId, List<string> permCodes);
Task<List<Permission>> UpdatePermissions(string employeeId, AdditionalPermissionRequest request);
public Task<AppUser> TryFindSingleUserByName(UserNameSearchRequest searchRequest);
Task<AppUser> FindManager(AppUser appUser);
Task<List<AppUser>> FindDepartmentManagers(int departmentId);
Task<bool> UserHasAnyRequiredPermission(string appUserEmail, List<string> allowedPermissions);
Task<bool> SyncActiveUserIds();
Task<IReadOnlyCollection<UserSummary>> GetAllUserSummariesAsync();
IReadOnlyCollection<UserSummary> GetTerminatedUsersAsync(MonthRange monthRange);
Task<EmployeeDetailsUpdateResult> UpdateEmployeeDetails(EmployeeDetailsUpdate employeeDetailsUpdate);
}
@@ -0,0 +1,12 @@
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.Library.Internal.IncentiveApiModels.Results;
using LCU.Library.Internal.LCUServiceFactory.Services;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.IncentiveProcesses;
public interface IIncentiveProcessesService : ILcuService
{
Task<ProcessResult> SyncLatestActivitiesFromDna(IncentiveProcessRequest request);
Task<List<IExternalActivity>> ProcessLatestActivity();
}
@@ -0,0 +1,13 @@
using LCU.Library.Internal.LCUServiceFactory.Configurations;
using LCU.Library.Internal.LCUServiceFactory.Services;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.IncentiveProcesses;
public abstract class IncentiveProcessesService : LcuAbstractService
{
public IncentiveProcessesService(ServicesFactoryConfiguration servicesFactoryConfiguration)
: base(servicesFactoryConfiguration.GetServiceFactoryRegistrationByInterface<IIncentiveProcessesService>())
{
}
}
@@ -0,0 +1,187 @@
using LCU.API.Internal.Operations.IncentiveApi.Configuration;
using LCU.API.Internal.Operations.IncentiveApi.DataAccess;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using NewAtmCheckCardActivity = LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities.NewAtmCheckCardActivity;
using NewCheckingAccountActivity = LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities.NewCheckingAccountActivity;
using NewLoanSaleActivity = LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities.NewLoanSaleActivity;
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Accounts;
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Activity;
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Transaction;
using System.Web;
using LCU.Library.Internal.Exceptions;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices;
public class CoreApiWrapperService : ICoreApiWrapperService
{
private readonly ICoreApiWrapperDao _coreApiWrapperDao;
private readonly CoreApiWrapperConfiguration _coreApiWrapperConfiguration;
public CoreApiWrapperService(CoreApiWrapperConfiguration coreApiWrapperConfiguration, ICoreApiWrapperDao coreApiWrapperDao)
{
_coreApiWrapperDao = coreApiWrapperDao;
_coreApiWrapperConfiguration = coreApiWrapperConfiguration;
}
public async Task<IReadOnlyCollection<NewCheckingAccountActivity>> GetNewCheckingAccountSalesActivity(DateTime activityDt)
{
try
{
return await _coreApiWrapperDao.GetNewCheckingAccountSalesActivity(activityDt);
}
catch (Exception ex)
{
var error = $"Failed to get New Checking Account Sales from DNA after: {activityDt} Exception Message: {ex.Message}";
Log.Error(error);
throw new ServiceException(error);
}
}
public async Task<IReadOnlyCollection<NewCheckingAccountActivity>> GetNewCheckingDigitalBankingAccountSalesActivity(DateTime activityDt)
{
try
{
return await _coreApiWrapperDao.GetNewCheckingDigitalBankingAccountSalesActivity(activityDt);
}
catch (Exception ex)
{
var error = $"Failed to get New Digital Banking Checking Account Sales from DNA after: {activityDt} Exception Message: {ex.Message}";
Log.Error(error);
throw new ServiceException(error);
}
}
public async Task<IReadOnlyCollection<NewAtmCheckCardActivity>> GetNewAtmCheckCardSalesActivity(DateTime activityDt)
{
try
{
return await _coreApiWrapperDao.GetNewAtmCheckCardSalesActivity(activityDt);
}
catch (Exception ex)
{
var error = $"Failed to get New ATM and Check Card Sales from DNA after: {activityDt} Exception Message: {ex.Message}";
Log.Error(error);
throw new ServiceException(error);
}
}
public async Task<IReadOnlyCollection<NewActivityManagerReferral>> GetNewActivityManagerReferralsActivity(DateTime activityDt)
{
try
{
return await _coreApiWrapperDao.GetNewActivityManagerReferralsActivity(activityDt);
}
catch (Exception ex)
{
var error = $"Failed to get New Activity Manager Referrals from DNA after: {activityDt} Exception Message: {ex.Message}";
Log.Error(error);
throw new ServiceException(error);
}
}
public async Task<IReadOnlyCollection<NewLoanSaleActivity>> GetNewMortgageSaleActivities(DateTime activityDt)
{
try
{
return await _coreApiWrapperDao.GetNewMortgageSaleActivities(activityDt);
}
catch (Exception ex)
{
var error = $"Failed to get New Mortgage Sales from DNA after: {activityDt} Exception Message: {ex.Message}";
Log.Error(error);
throw new ServiceException(error);
}
}
public async Task<IReadOnlyCollection<NewLoanSaleActivity>> GetNewVehicleSaleActivities(DateTime activityDt)
{
try
{
return await _coreApiWrapperDao.GetNewVehicleSaleActivities(activityDt);
}
catch (Exception ex)
{
var error = $"Failed to get New Vehicle Sales from DNA after: {activityDt} Exception Message: {ex.Message}";
Log.Error(error);
throw new ServiceException(error);
}
}
public async Task<IReadOnlyCollection<NewLoanSaleActivity>> GetNewDpEligibleSaleActivities(DateTime activityDt)
{
try
{
return await _coreApiWrapperDao.GetNewDpEligibleSaleActivities(activityDt);
}
catch (Exception ex)
{
var error = $"Failed to get New DP Eligible Sales and Actvities from DNA after: {activityDt} Exception Message: {ex.Message}";
Log.Error(error);
throw new ServiceException(error);
}
}
public async Task<IReadOnlyCollection<ActivityAccountDetails>> GetUpdatedAccountInformation(List<long> accountNumbers)
{
try
{
return await _coreApiWrapperDao.GetUpdatedAccountInformation(accountNumbers);
}
catch (Exception ex)
{
var error = $"Failed to get Updated Account Information from DNA after: {string.Join(",", accountNumbers)}" + $"Exception Message: {ex.Message}";
Log.Error(error);
throw new ServiceException(error);
}
}
public async Task<IReadOnlyCollection<NewActivityManagerReferral>> GetActivityManagerActivities(ActivityQueryRequest request)
{
try
{
return await _coreApiWrapperDao.GetActivityManagerActivities(request);
}
catch (Exception ex)
{
var error = $"Failed to get Activity Manager Activities from DNA after: {request.ActivityIds} Exception Message: {ex.Message}";
Log.Error(error);
throw new ServiceException(error);
}
}
public async Task<IReadOnlyCollection<TransactionHistory>> GetTransactionHistory(string accountNumber, DateTime startDate, DateTime endDate, List<string> transactionCodes)
{
try
{
return await _coreApiWrapperDao.GetTransactionHistory(accountNumber, startDate, endDate, transactionCodes);
}
catch (Exception ex)
{
var error = $"Failed to get Transaction History for Account Number: {accountNumber} Exception Message: {ex.Message}";
Log.Error(error);
throw new ServiceException(error);
}
}
public string DateTimeToUrlString(DateTime dateTime)
{
var dtstring = dateTime.ToString("yyyy-MM-ddTHH:mm:ss");
return HttpUtility.UrlEncode(dtstring);
}
public async Task<IReadOnlyCollection<EStatementDeliveryMethod>> GetEStatmentDeliveryMethods(DateTime activityDt)
{
try
{
return await _coreApiWrapperDao.GetEStatmentDeliveryMethods(activityDt);
}
catch (Exception ex)
{
var error = $"Failed to get EStatement Delivery Methods from DNA after: {activityDt} Exception Message: {ex.Message}";
Log.Error(error);
throw new ServiceException(error);
}
}
}
@@ -0,0 +1,43 @@
using LCU.API.Internal.Operations.IncentiveApi.DataAccess;
using LCU.Library.Internal.Exceptions;
using LCU.Library.Internal.FileOperationsModels.Models;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices;
public class FileOperationsApiService : IFileOperationsApiService
{
private readonly IFileOperationsApiDao _fileOperationDao;
public FileOperationsApiService(IFileOperationsApiDao fileOperationDao)
{
_fileOperationDao = fileOperationDao;
}
public async Task<string[]> BuildDynamicFileList(DynamicFileListRequest dynamicFileRequest)
{
try
{
return await _fileOperationDao.BuildDynamicFileList(dynamicFileRequest);
}
catch (Exception exception)
{
var message = $"Error to BuildDynamicFileList. FilePath: {dynamicFileRequest.Path}.";
Log.Error(exception, message);
throw new ServiceException($"{message} Exception Message:{exception.Message}");
}
}
public async Task<string> GetFileContent(string filePath)
{
try
{
return await _fileOperationDao.GetFileContent(filePath);
}
catch (Exception exception)
{
var message = $"Failure to GetFileContent. FilePath: {filePath}.";
Log.Error(exception, message);
throw new ServiceException($"{message} Exception Message: {exception.Message}");
}
}
}
@@ -0,0 +1,25 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Accounts;
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Activity;
using LCU.Library.Internal.CoreApiWrapperModel.CoreModels.Transaction;
using NewAtmCheckCardActivity = LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities.NewAtmCheckCardActivity;
using NewCheckingAccountActivity = LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities.NewCheckingAccountActivity;
using NewLoanSaleActivity = LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities.NewLoanSaleActivity;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices;
public interface ICoreApiWrapperService
{
public Task<IReadOnlyCollection<EStatementDeliveryMethod>> GetEStatmentDeliveryMethods(DateTime activityDt);
public Task<IReadOnlyCollection<NewCheckingAccountActivity>> GetNewCheckingDigitalBankingAccountSalesActivity(DateTime activityDt);
public Task<IReadOnlyCollection<NewCheckingAccountActivity>> GetNewCheckingAccountSalesActivity(DateTime activityDt);
public Task<IReadOnlyCollection<NewAtmCheckCardActivity>> GetNewAtmCheckCardSalesActivity(DateTime activityDt);
public Task<IReadOnlyCollection<NewActivityManagerReferral>> GetNewActivityManagerReferralsActivity(DateTime activityDt);
public Task<IReadOnlyCollection<NewLoanSaleActivity>> GetNewMortgageSaleActivities(DateTime activityDt);
public Task<IReadOnlyCollection<NewLoanSaleActivity>> GetNewVehicleSaleActivities(DateTime activityDt);
public Task<IReadOnlyCollection<NewLoanSaleActivity>> GetNewDpEligibleSaleActivities(DateTime activityDt);
public Task<IReadOnlyCollection<ActivityAccountDetails>> GetUpdatedAccountInformation(List<long> activityIds);
public Task<IReadOnlyCollection<NewActivityManagerReferral>> GetActivityManagerActivities(ActivityQueryRequest request);
public Task<IReadOnlyCollection<TransactionHistory>> GetTransactionHistory(string accountNumber, DateTime startDate, DateTime endDate, List<string> transactionCodes);
}
@@ -0,0 +1,9 @@
using LCU.Library.Internal.FileOperationsModels.Models;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices;
public interface IFileOperationsApiService
{
Task<string[]> BuildDynamicFileList(DynamicFileListRequest dynamicFileRequest);
Task<string> GetFileContent(string filePath);
}
@@ -0,0 +1,13 @@
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce;
using LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices;
public interface INotificationApiService
{
public Task SendMonthStartGoalDispersalReceipt(Dictionary<string, GoalDispersalReceipt> receipts, List<string> recipients);
public Task SendMissingRoleNotification(IEnumerable<AppUser> usersMissingRole, List<string> recipients);
public Task SendTerminatedEmployeeNotification(IEnumerable<TerminatedEmployeeReceipt> terminated, List<string> recipients);
public Task SendTransferRequestInitiatedNotification(ActivityTransferAction transferAction, List<string> recipients);
}
@@ -0,0 +1,312 @@
using LCU.API.Internal.Operations.IncentiveApi.Configuration;
using LCU.API.Internal.Operations.IncentiveApi.DataAccess;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce;
using LCU.API.Internal.Operations.IncentiveApi.Models.GoalDispersal;
using LCU.API.Internal.Operations.IncentiveApiLib.Services;
using LCU.API.Internal.NotificationsApiLib.Models;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices;
public class NotificationApiService : INotificationApiService
{
private readonly EmailRecipientsConfiguration _emailRecipientsConfig;
private readonly INotificationApiDao _notificationsDao;
private readonly ApiConfiguration _incentiveApiConfig;
public NotificationApiService(EmailRecipientsConfiguration emailRecipientsConfig, ApiConfiguration incentiveApiConfig, INotificationApiDao notificationsDao,
IUserService userService)
{
_emailRecipientsConfig = emailRecipientsConfig;
_notificationsDao = notificationsDao;
_incentiveApiConfig = incentiveApiConfig;
}
public async Task SendMonthStartGoalDispersalReceipt(Dictionary<string, GoalDispersalReceipt> receipts, List<string> recipients)
{
try
{
#region Body HTML Boilerplate
string body =
@"<style>
.report {
border:1px solid #C0C0C0;
border-collapse:collapse;
padding:5px;
}
.report th {
border:1px solid #C0C0C0;
padding:5px;
background:#F0F0F0;
}
.report td {
border:1px solid #C0C0C0;
padding:5px;
}
</style>
<table class='report'>
<tbody>
<tr>
<td><h3>Department</h3></td>
<td><h3>Strategy</h3></td>
<td><h3>Alerts</h3></td>
</tr>";
#endregion
foreach (var (departmentName, receipt) in receipts)
{
if (receipt == null)
{
body += $@"
<tr>
<td><b>{departmentName}</b></td>
<td>&nbsp;<i>FAILED</i></td>
<td>&nbsp;No Receipt Data, Failed to Run Dispersal.</td>
</tr>";
}
else if (receipt.GoalDispersalErrors.Any())
{
body += $@"
<tr>
<td><b>{departmentName}</b></td>
<td>&nbsp;<i>{receipt.Strategy}</i></td>
<td>&nbsp;{FormatReceiptItems(receipt.GoalDispersalErrors)}</td>
</tr>";
}
}
body += @"</tbody> </table>";
var emailRequest = new EmailRequest
{
Recipient = recipients,
Subject = "Month Start Personnel Dispersal Report",
Body = body
};
await _notificationsDao.SendMessageAsync(emailRequest);
}
catch (Exception ex)
{
Log.Error(ex, "Failed to Send Month Start Dispersal Report");
}
}
public async Task SendMissingRoleNotification(IEnumerable<AppUser> usersMissingRole, List<string> recipients)
{
try
{
#region Body HTML Boilerplate
string body =
@"<style>
.report {
border:1px solid #C0C0C0;
border-collapse:collapse;
padding:5px;
}
.report th {
border:1px solid #C0C0C0;
padding:5px;
background:#F0F0F0;
}
.report td {
border:1px solid #C0C0C0;
padding:5px;
}
</style>
<table class='report'>
<tbody>
<tr>
<td><h3>Name</h3></td>
<td><h3>Employee ID</h3></td>
<td><h3>Department</h3></td>
<td><h3>Missing Role</h3></td>
</tr>";
#endregion
foreach (var user in usersMissingRole)
{
body += $@"
<tr>
<td>{user.FullName}</td>
<td>{user.EmployeeId}</td>
<td>{user.HomeDepartment}</td>
<td>{user.BaseRole}</td>
</tr>";
}
body += @"</tbody> </table>";
var emailRequest = new EmailRequest
{
Recipient = recipients,
Subject = $"Users missing Dayforce role",
Body = body
};
await _notificationsDao.SendMessageAsync(emailRequest);
}
catch (Exception ex)
{
Log.Error(ex, "Failed to send missing role notification");
}
}
public async Task SendTerminatedEmployeeNotification(IEnumerable<TerminatedEmployeeReceipt> terminated, List<string> recipients)
{
try
{
#region Body HTML Boilerplate
string body =
@"<style>
.report {
border:1px solid #C0C0C0;
border-collapse:collapse;
padding:5px;
}
.report th {
border:1px solid #C0C0C0;
padding:5px;
background:#F0F0F0;
}
.report td {
border:1px solid #C0C0C0;
padding:5px;
}
</style>
<table class='report'>
<tbody>
<tr>
<td><h3>Name</h3></td>
<td><h3>Employee ID</h3></td>
<td><h3>Department</h3></td>
<td><h3>Terminated</h3></td>
<td><h3>Manager</h3></td>
<td><h3>Prev Month Payout</h3></td>
<td><h3>This Month Payout</h3></td>
</tr>";
#endregion
foreach (var receipt in terminated)
{
var manager = receipt.Manager != null ? receipt.Manager.FullName : "Not Found";
body += $@"
<tr>
<td>{receipt.Terminated.FullName}</td>
<td>{receipt.Terminated.EmployeeId}</td>
<td>{receipt.Terminated.HomeDepartment}</td>
<td>{receipt.Terminated.Terminated.Value:MM/dd/yyyy}</td>
<td>{manager}</td>
<td>${receipt.LastMonthPayout.PayoutTotal}.00</td>
<td>${receipt.ThisMonthPayout.PayoutTotal}.00</td>
</tr>";
}
body += @"</tbody> </table>";
var emailRequest = new EmailRequest
{
Recipient = recipients,
Subject = $"Users terminated in Dayforce",
Body = body
};
await _notificationsDao.SendMessageAsync(emailRequest);
}
catch (Exception ex)
{
Log.Error(ex, "Failed to send terminated employee notification");
}
}
private string FormatReceiptItems(List<GoalDispersalReceiptItem> receiptErrors)
{
string orderedList = @"<ol>";
foreach (var error in receiptErrors)
{
orderedList += $@"<li> <b>{error.EmployeeName}</b> - {error.EmployeeId}: {error.Message}<br><br></li>";
}
orderedList += @"</ol>";
return orderedList;
}
public async Task SendTransferRequestInitiatedNotification(ActivityTransferAction transferAction,
List<string> recipients)
{
try
{
#region Body HTML Boilerplate
string body =
@"<style>
.report {
border:1px solid #C0C0C0;
border-collapse:collapse;
padding:5px;
}
.report th {
border:1px solid #C0C0C0;
padding:5px;
background:#F0F0F0;
}
.report td {
border:1px solid #C0C0C0;
padding:5px;
}
</style>";
#endregion
body += @"
<p>A transfer request has been initiated for the following activity:</p>
<table class='report'>
<tbody>
<tr>
<td><h3>Product</h3></td>
<td><h3>Activity Type</h3></td>
<td><h3>Requested by</h3></td>
<td><h3>Transfer to Employee</h3></td>
<td><h3>Transfer to Department</h3></td>
</tr>";
body += $@"
<tr>
<td>{transferAction.RequestedActivity.Product}</td>
<td>{transferAction.RequestedActivity.Type}</td>
<td>{transferAction.CreatedBy}</td>
<td>{transferAction.NewActivityEmployeeId}</td>
<td>{transferAction.NewActivityDepartmentId}</td>
</tr>
</tbody>
</table>";
var webAppUrl = $@"{_incentiveApiConfig.IncentiveManagerClientUrl}/activity/{transferAction.RequestedActivityId}";
body += $@"
<p>
The request can be approved at <a href='{webAppUrl}'>{webAppUrl}</a>.
</p>";
if (_emailRecipientsConfig.OverrideRecipients)
{
body += $"<p>The original recipients have been overriden in this environment: {string.Join(", ", recipients)}</p>";
recipients = _emailRecipientsConfig.TransferRequestInitiatedNotification;
}
var emailRequest = new EmailRequest
{
Recipient = recipients,
Subject = "Activity transfer request initiated",
Body = body
};
await _notificationsDao.SendMessageAsync(emailRequest);
}
catch (Exception ex)
{
Log.Error(ex, "Failed to send transfer request initiated notification");
}
}
}
@@ -0,0 +1,66 @@
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.Library.Internal.IncentiveApiModels.Results;
using LCU.Library.Internal.LCUServiceFactory.Configurations;
using Microsoft.Extensions.DependencyInjection;
using LCU.API.Internal.Operations.IncentiveApi.Services.NightlyIncentiveProcesses;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.NightlyDecisionServices;
public class AccountActivityDecisionService : NightlyIncentiveProcessesService, INightlyIncentiveProcessesService
{
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly ProcessResult _processResult;
public AccountActivityDecisionService(ServicesFactoryConfiguration servicesFactoryConfiguration,
IServiceScopeFactory serviceScopeFactory)
: base(servicesFactoryConfiguration)
{
_serviceScopeFactory = serviceScopeFactory;
_processResult = new ProcessResult();
}
public async Task<ProcessResult> RunProductDecisionProcess(NightlyIncentiveProcessRequest request)
{
try
{
var result = await UpdateAndDecideActivities();
var message = $"{result.Count()} activities were updated";
Log.Information(message);
_processResult.MessageStack.Add(message);
}
catch (Exception exception)
{
var message = $"There was an issue with the Account Update process. Exception Message: {exception.Message}.";
_processResult.MessageStack.Add(message);
_processResult.Success = false;
}
return _processResult;
}
public async Task<IReadOnlyCollection<ActivityStateChange>> UpdateAndDecideActivities()
{
var listChanges = new List<ActivityStateChange>();
using var scope = _serviceScopeFactory.CreateScope();
var changes = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IActivityDecisionService>().DecisionActivityByState(ActivityState.Created));
Log.Information($"Auto-decisioned {changes.Count} activities in Created state.");
listChanges.AddRange(changes.ToList());
changes = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IActivityDecisionService>().DecisionActivityByState(ActivityState.ManualReview));
Log.Information($"Auto-decisioned {changes.Count} activities in Manual Review state.");
listChanges.AddRange(changes.ToList());
changes = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IActivityDecisionService>().UpdateAndDecideManualReviewActivities());
Log.Information($"Updated and Auto-Decisioned {changes.Count} Activities.");
listChanges.AddRange(changes.ToList());
changes = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IActivityDecisionService>().UpdateAndDecideDpEligibleLoanActivities());
Log.Information($"Updated and Auto-Decisioned {changes.Count} Debt Protection Eligible Loan Activities.");
listChanges.AddRange(changes.ToList());
return listChanges;
}
}
@@ -0,0 +1,115 @@
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.Library.Internal.IncentiveApiModels.Results;
using LCU.Library.Internal.LCUServiceFactory.Configurations;
using Microsoft.Extensions.DependencyInjection;
using LCU.API.Internal.Operations.IncentiveApi.Services.NightlyIncentiveProcesses;
using LCU.Library.Internal.IncentiveApiModels.Common;
using LCU.Library.Internal.IncentiveApiModels.Entities;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.NightlyDecisionServices;
public class DirectDepositActivityDecisionService : NightlyIncentiveProcessesService, INightlyIncentiveProcessesService
{
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly ProcessResult _processResult;
private const int PageItemLimit = 1000;
private static readonly List<string> TransactionCodes = new List<string> { "XDEP" };
public DirectDepositActivityDecisionService(ServicesFactoryConfiguration servicesFactoryConfiguration,
IServiceScopeFactory serviceScopeFactory)
: base(servicesFactoryConfiguration)
{
_serviceScopeFactory = serviceScopeFactory;
_processResult = new ProcessResult();
}
public async Task<ProcessResult> RunProductDecisionProcess(NightlyIncentiveProcessRequest request)
{
try
{
var result = await UpdateAndDecideActivities();
var message = $"{result.Count()} Direct Deposit activities were updated";
Log.Information(message);
_processResult.MessageStack.Add(message);
}
catch (Exception exception)
{
var message = $"There was an issue with the Direct Deposit Update Process. Exception Message: {exception.Message}.";
_processResult.MessageStack.Add(message);
_processResult.Success = false;
}
return _processResult;
}
public async Task<IReadOnlyCollection<ActivityStateChange>> UpdateAndDecideActivities()
{
using var scope = _serviceScopeFactory.CreateScope();
var activityPage = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IActivityService>().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 Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IDnaService>().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 Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IActivityService>().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 Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IActivityService>().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;
}
}
@@ -0,0 +1,48 @@
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.Library.Internal.IncentiveApiModels.Results;
using LCU.Library.Internal.LCUServiceFactory.Configurations;
using Microsoft.Extensions.DependencyInjection;
using LCU.API.Internal.Operations.IncentiveApi.Services.NightlyIncentiveProcesses;
using LCU.Library.Internal.IncentiveApiModels.Entities;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.NightlyDecisionServices;
public class EStatementReferralActivityDecisionService : NightlyIncentiveProcessesService, INightlyIncentiveProcessesService
{
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly ProcessResult _processResult;
public EStatementReferralActivityDecisionService(ServicesFactoryConfiguration servicesFactoryConfiguration,
IServiceScopeFactory serviceScopeFactory)
: base(servicesFactoryConfiguration)
{
_serviceScopeFactory = serviceScopeFactory;
_processResult = new ProcessResult();
}
public async Task<ProcessResult> RunProductDecisionProcess(NightlyIncentiveProcessRequest request)
{
try
{
var result = await UpdateAndDecideActivities();
var message = $"{result.Count()} E-Statement Referrals were updated";
Log.Information(message);
_processResult.MessageStack.Add(message);
}
catch (Exception exception)
{
var message = $"There was an issue with the E-Statement Referral Update process. Exception Message: {exception.Message}.";
_processResult.MessageStack.Add(message);
_processResult.Success = false;
}
return _processResult;
}
public async Task<IReadOnlyCollection<ActivityStateChange>> UpdateAndDecideActivities()
{
using var scope = _serviceScopeFactory.CreateScope();
var changes = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IEStatementService>().UpdateOrExpireEStatementReferral());
return changes;
}
}
@@ -0,0 +1,8 @@
using LCU.Library.Internal.IncentiveApiModels.Requests;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.NightlyDecisionServices;
public interface INightlyActivityDecisionService
{
Task HandleProcessRequest(NightlyIncentiveProcessRequest request);
}
@@ -0,0 +1,86 @@
using LCU.Library.Internal.IncentiveApiModels.Enums;
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.Library.Internal.IncentiveApiModels.Results;
using LCU.Library.Internal.LCUServiceFactory.Configurations;
using Microsoft.Extensions.DependencyInjection;
using LCU.API.Internal.Operations.IncentiveApi.Services.NightlyIncentiveProcesses;
using LCU.API.Internal.Operations.IncentiveApi.Services.AutoDecision;
using LCU.Library.Internal.IncentiveApiModels.Common;
using LCU.Library.Internal.IncentiveApiModels.Entities;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.NightlyDecisionServices;
public class LICActivityDecisionService : NightlyIncentiveProcessesService, INightlyIncentiveProcessesService
{
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly ProcessResult _processResult;
private const int PageItemLimit = 1000;
public LICActivityDecisionService(ServicesFactoryConfiguration servicesFactoryConfiguration,
IServiceScopeFactory serviceScopeFactory)
: base(servicesFactoryConfiguration)
{
_serviceScopeFactory = serviceScopeFactory;
_processResult = new ProcessResult();
}
public async Task<ProcessResult> RunProductDecisionProcess(NightlyIncentiveProcessRequest request)
{
try
{
var result = await UpdateAndDecideActivities();
var message = $"{result.Count()} LIC activities were updated";
Log.Information(message);
_processResult.MessageStack.Add(message);
}
catch (Exception exception)
{
var message = $"There was an issue with the LIC Product Update process. Exception Message: {exception.Message}.";
_processResult.MessageStack.Add(message);
_processResult.Success = false;
}
return _processResult;
}
public async Task<IReadOnlyCollection<ActivityStateChange>> UpdateAndDecideActivities()
{
using var scope = _serviceScopeFactory.CreateScope();
var changes = new List<ActivityStateChange>();
var allItemsProcessed = false;
var page = 0;
var processedItemCount = 0;
while (!allItemsProcessed)
{
try
{
var activityPage = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IActivityService>().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 Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IActivityDetailService>().TryUpdateLicReferralStationDetail(activities));
changes.AddRange(await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IActivityDecisionService>().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;
}
}
@@ -0,0 +1,46 @@
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.Library.Internal.IncentiveApiModels.Results;
using LCU.Library.Internal.LCUServiceFactory.Services;
using LCU.API.Internal.Operations.IncentiveApi.Services.NightlyIncentiveProcesses;
using LCU.Library.Internal.ApiResponse.Services;
using LCU.Library.Internal.Exceptions;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.NightlyDecisionServices;
public class NightlyActivityDecisionService : INightlyActivityDecisionService
{
private ProcessResult _processResult;
private INightlyIncentiveProcessesService _nightlyIncentiveProcessesService;
private readonly ILcuServiceFactory<INightlyIncentiveProcessesService> _lcuServiceFactory;
public NightlyActivityDecisionService(ILcuServiceFactory<INightlyIncentiveProcessesService> lcuServiceFactory)
{
_nightlyIncentiveProcessesService = null;
_lcuServiceFactory = lcuServiceFactory;
_processResult = new ProcessResult();
}
public async Task HandleProcessRequest(NightlyIncentiveProcessRequest request)
{
try
{
ArgumentNullException.ThrowIfNull(request);
_nightlyIncentiveProcessesService = _lcuServiceFactory.GetService(request.NightlyIncentiveProcessType);
ArgumentNullException.ThrowIfNull(_nightlyIncentiveProcessesService);
var message = $"Queued Request for {request.NightlyIncentiveProcessType}";
Log.Information(message);
_processResult = await _nightlyIncentiveProcessesService.RunProductDecisionProcess(request);
await QueueReturnResponse.QueueResponse(request, _processResult.Success, _processResult.MessageStack);
}
catch (Exception exception)
{
var message = $"There was an issue with handling the Daily Decision Process Exception Message: {exception.Message}.";
_processResult.MessageStack.Add(message);
_processResult.Success = false;
await QueueReturnResponse.QueueResponse(request, _processResult.Success, _processResult.MessageStack);
throw new ServiceException(message);
}
}
}
@@ -0,0 +1,48 @@
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.Library.Internal.IncentiveApiModels.Results;
using LCU.Library.Internal.LCUServiceFactory.Configurations;
using Microsoft.Extensions.DependencyInjection;
using LCU.API.Internal.Operations.IncentiveApi.Services.NightlyIncentiveProcesses;
using LCU.Library.Internal.IncentiveApiModels.Entities;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.NightlyDecisionServices;
public class ReferralActivityDecisionService : NightlyIncentiveProcessesService, INightlyIncentiveProcessesService
{
private readonly IServiceScopeFactory _serviceScopeFactory;
private readonly ProcessResult _processResult;
public ReferralActivityDecisionService(ServicesFactoryConfiguration servicesFactoryConfiguration,
IServiceScopeFactory serviceScopeFactory)
: base(servicesFactoryConfiguration)
{
_serviceScopeFactory = serviceScopeFactory;
_processResult = new ProcessResult();
}
public async Task<ProcessResult> RunProductDecisionProcess(NightlyIncentiveProcessRequest request)
{
try
{
var result = await UpdateAndDecideActivities();
var message = $"{result.Count()} Referrals were updated";
Log.Information(message);
_processResult.MessageStack.Add(message);
}
catch (Exception exception)
{
var message = $"There was an issue with the Referral Update process. Exception Message: {exception.Message}.";
_processResult.MessageStack.Add(message);
_processResult.Success = false;
}
return _processResult;
}
public async Task<IReadOnlyCollection<ActivityStateChange>> UpdateAndDecideActivities()
{
using var scope = _serviceScopeFactory.CreateScope();
var changes = await Task.Run(async () => await scope.ServiceProvider.GetRequiredService<IActivityService>().MatchOrExpireReferrals());
return changes;
}
}
@@ -0,0 +1,13 @@
using LCU.API.Internal.Operations.IncentiveApi.Models.ExternalActivities;
using LCU.Library.Internal.IncentiveApiModels.Entities;
using LCU.Library.Internal.IncentiveApiModels.Requests;
using LCU.Library.Internal.IncentiveApiModels.Results;
using LCU.Library.Internal.LCUServiceFactory.Services;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.NightlyIncentiveProcesses;
public interface INightlyIncentiveProcessesService : ILcuService
{
Task<ProcessResult> RunProductDecisionProcess(NightlyIncentiveProcessRequest request);
Task<IReadOnlyCollection<ActivityStateChange>> UpdateAndDecideActivities();
}
@@ -0,0 +1,13 @@
using LCU.Library.Internal.LCUServiceFactory.Configurations;
using LCU.Library.Internal.LCUServiceFactory.Services;
namespace LCU.API.Internal.Operations.IncentiveApi.Services.NightlyIncentiveProcesses;
public abstract class NightlyIncentiveProcessesService : LcuAbstractService
{
public NightlyIncentiveProcessesService(ServicesFactoryConfiguration servicesFactoryConfiguration)
: base(servicesFactoryConfiguration.GetServiceFactoryRegistrationByInterface<INightlyIncentiveProcessesService>())
{
}
}

Some files were not shown because too many files have changed in this diff Show More