Files
2026-03-17 09:53:09 -05:00

287 lines
12 KiB
C#

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.");
}
}
}