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