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

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