394 lines
17 KiB
C#
394 lines
17 KiB
C#
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.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);
|
|
}
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|