Adding
This commit is contained in:
+160
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+172
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -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;
|
||||
}
|
||||
+12
@@ -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);
|
||||
}
|
||||
+224
@@ -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);
|
||||
}
|
||||
}
|
||||
+748
@@ -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);
|
||||
}
|
||||
}
|
||||
+395
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
@@ -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);
|
||||
}
|
||||
+153
@@ -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;
|
||||
}
|
||||
}
|
||||
+413
@@ -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;
|
||||
}
|
||||
}
|
||||
+24
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
+78
@@ -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;
|
||||
}
|
||||
}
|
||||
+57
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
+56
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
+52
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
+18
@@ -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);
|
||||
}
|
||||
}
|
||||
+10
@@ -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);
|
||||
}
|
||||
}
|
||||
+31
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
+50
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
+58
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
+67
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
+70
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
+56
@@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
+49
@@ -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();
|
||||
}
|
||||
}
|
||||
+110
@@ -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;
|
||||
}
|
||||
}
|
||||
+13
@@ -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;
|
||||
}
|
||||
}
|
||||
+58
@@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
+78
@@ -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;
|
||||
}
|
||||
}
|
||||
+293
@@ -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;
|
||||
}
|
||||
}
|
||||
+9
@@ -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);
|
||||
}
|
||||
+14
@@ -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);
|
||||
}
|
||||
+24
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+98
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+244
@@ -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;
|
||||
}
|
||||
}
|
||||
+286
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
+17
@@ -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);
|
||||
}
|
||||
+97
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+100
@@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
+96
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+14
@@ -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);
|
||||
}
|
||||
+93
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+95
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+93
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+93
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+93
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
+52
@@ -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()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
+42
@@ -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();
|
||||
}
|
||||
}
|
||||
+33
@@ -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;
|
||||
}
|
||||
}
|
||||
+28
@@ -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}");
|
||||
}
|
||||
}
|
||||
+8
@@ -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);
|
||||
}
|
||||
+9
@@ -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);
|
||||
}
|
||||
+33
@@ -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;
|
||||
}
|
||||
}
|
||||
+32
@@ -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;
|
||||
}
|
||||
}
|
||||
+127
@@ -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 });
|
||||
}
|
||||
}
|
||||
+28
@@ -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;
|
||||
}
|
||||
}
|
||||
+9
@@ -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);
|
||||
}
|
||||
+11
@@ -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);
|
||||
|
||||
}
|
||||
+74
@@ -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;
|
||||
}
|
||||
}
|
||||
+69
@@ -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;
|
||||
}
|
||||
}
|
||||
+72
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
+97
@@ -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();
|
||||
}
|
||||
}
|
||||
+114
@@ -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();
|
||||
}
|
||||
}
|
||||
+77
@@ -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;
|
||||
}
|
||||
}
|
||||
+243
@@ -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);
|
||||
}
|
||||
}
|
||||
+215
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
+254
@@ -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;
|
||||
}
|
||||
}
|
||||
+13
@@ -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);
|
||||
}
|
||||
+34
@@ -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);
|
||||
}
|
||||
+13
@@ -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);
|
||||
}
|
||||
+9
@@ -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);
|
||||
}
|
||||
+14
@@ -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);
|
||||
}
|
||||
+13
@@ -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);
|
||||
}
|
||||
+19
@@ -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);
|
||||
}
|
||||
+10
@@ -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);
|
||||
}
|
||||
+13
@@ -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);
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
using LCU.Library.Internal.IncentiveApiModels.Entities;
|
||||
|
||||
namespace LCU.API.Internal.Operations.IncentiveApi.Services;
|
||||
|
||||
public interface IEStatementService
|
||||
{
|
||||
Task<IReadOnlyCollection<ActivityStateChange>> UpdateOrExpireEStatementReferral();
|
||||
}
|
||||
+8
@@ -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);
|
||||
}
|
||||
+13
@@ -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);
|
||||
}
|
||||
+19
@@ -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);
|
||||
}
|
||||
+10
@@ -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);
|
||||
}
|
||||
+14
@@ -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);
|
||||
}
|
||||
+14
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
+36
@@ -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);
|
||||
}
|
||||
+12
@@ -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();
|
||||
}
|
||||
+13
@@ -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>())
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
+187
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+43
@@ -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}");
|
||||
}
|
||||
}
|
||||
}
|
||||
+25
@@ -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);
|
||||
}
|
||||
+9
@@ -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);
|
||||
}
|
||||
+13
@@ -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);
|
||||
}
|
||||
+312
@@ -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> <i>FAILED</i></td>
|
||||
<td> No Receipt Data, Failed to Run Dispersal.</td>
|
||||
</tr>";
|
||||
}
|
||||
else if (receipt.GoalDispersalErrors.Any())
|
||||
{
|
||||
body += $@"
|
||||
<tr>
|
||||
<td><b>{departmentName}</b></td>
|
||||
<td> <i>{receipt.Strategy}</i></td>
|
||||
<td> {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");
|
||||
}
|
||||
}
|
||||
}
|
||||
+66
@@ -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;
|
||||
}
|
||||
}
|
||||
+115
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+48
@@ -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;
|
||||
}
|
||||
}
|
||||
+8
@@ -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);
|
||||
}
|
||||
+86
@@ -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;
|
||||
}
|
||||
}
|
||||
+46
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
+48
@@ -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;
|
||||
}
|
||||
}
|
||||
+13
@@ -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();
|
||||
}
|
||||
+13
@@ -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
Reference in New Issue
Block a user