using LCU.API.Internal.Operations.IncentiveApi.Exceptions; using LCU.API.Internal.Operations.IncentiveApiLib.Persistence; using LCU.API.Internal.Operations.IncentiveApi.Models.DataTransfer; using LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; using LCU.Library.Internal.IncentiveApiModels.Entities; using LCU.Library.Internal.IncentiveApiModels.Requests; using LCU.Library.Internal.IncentiveApiModels.Common; using LCU.Library.Internal.IncentiveApiModels.Results; namespace LCU.API.Internal.Operations.IncentiveApiLib.Services; public class UserService : IUserService { private const string AutoGeneratedHomeAssignment = "Auto Generated Home Assignment"; private readonly IApplicationDbIntegrityService _applicationDbIntegrityService; private readonly IDepartmentRepository _departmentRepository; private readonly IRoleMappingRepository _roleMappingRepository; private readonly IUnitOfWork _unitOfWork; private readonly IUserDepartmentHistoryRepository _userDepartmentHistoryRepository; private readonly IUserPermissionHistoryRepository _userPermissionHistoryRepository; private readonly IUserRepository _userRepository; public UserService( IApplicationDbIntegrityService applicationDbIntegrityService, IDepartmentRepository departmentRepository, IRoleMappingRepository roleMappingRepository, IUnitOfWork unitOfWork, IUserDepartmentHistoryRepository userDepartmentHistoryRepository, IUserPermissionHistoryRepository userPermissionHistoryRepository, IUserRepository userRepository ) { _applicationDbIntegrityService = applicationDbIntegrityService; _departmentRepository = departmentRepository; _roleMappingRepository = roleMappingRepository; _unitOfWork = unitOfWork; _userDepartmentHistoryRepository = userDepartmentHistoryRepository; _userPermissionHistoryRepository = userPermissionHistoryRepository; _userRepository = userRepository; } public async Task> GetAllUserSummariesAsync() { return await _userRepository.GetAllUserSummariesAsync(); } public IReadOnlyCollection GetTerminatedUsersAsync(MonthRange monthRange) { return _userRepository.GetTerminatedUsersAsync(monthRange); } public async Task> GetAllUsersAsync(UserSearchRequest userSearchRequest) { if (string.IsNullOrWhiteSpace(userSearchRequest.Query) && string.IsNullOrWhiteSpace(userSearchRequest.Role) && string.IsNullOrWhiteSpace(userSearchRequest.FirstName) && string.IsNullOrWhiteSpace(userSearchRequest.LastName) && string.IsNullOrEmpty(userSearchRequest.DnaOrgNbr)) { return await _userRepository.GetAllUsersAsync(); } return await _userRepository.SearchUsersAsync(userSearchRequest); } public async Task GetUserByEmployeeIdAsync(string employeeId) { return await _userRepository.FindUserByEmployeeIdAsync(employeeId); } public async Task GetUserByAdEmailAsync(string adEmail) { return await _userRepository.FindUserByAdEmailAsync(adEmail); } public async Task GetActiveUserByEmployeeIdAsync(string employeeId) { return await _userRepository.FindActiveUserByEmployeeIdAsync(employeeId); } public async Task GetActiveUserByAdEmailAsync(string adEmail) { return await _userRepository.FindUserByAdEmailAsync(adEmail); } public async Task FindUserAsync(string employeeId, string adEmail) { return await _userRepository.FindUserAsync(employeeId, adEmail); } public async Task SaveUserAsync(AppUser newAppUser) { var user = await _userRepository.SaveUserAsync(newAppUser); await _unitOfWork.CompleteAsync(); return user; } public async Task> SaveUserRangeAsync(List newUsers) { var users = await _userRepository.SaveUserRangeAsync(newUsers); await _unitOfWork.CompleteAsync(); return users; } private async Task SyncDayForceEmployeeExisting(AppUser existing, ActiveEmployeeResult activeEmployee) { var homeUserDepartment = existing.Departments.FirstOrDefault(x => x.IsHome && x.EndDate == null); var salesforceDepartment = await LookupDepartment(activeEmployee); if (homeUserDepartment == null && existing.Terminated != null) { homeUserDepartment = await GenerateNewEmployeeHomeDepartment(existing, activeEmployee); existing.Departments.Add(homeUserDepartment); existing.Terminated = null; } if (homeUserDepartment == null) { throw new Exception($"Existing user with no home department, {existing.EmployeeId}"); } var homeDepartmentId = homeUserDepartment.Department == null ? homeUserDepartment.DepartmentId : homeUserDepartment.Department.Id; if (homeDepartmentId != salesforceDepartment.Id) { var monthBegin = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); var nextMonthBegin = monthBegin.AddMonths(1); var existingAssignment = existing.Departments.FirstOrDefault(x => x.SafeGetDepartmentId() == salesforceDepartment.Id && x.EndDate == null || x.EndDate >= monthBegin); if (existingAssignment != null) { existingAssignment.EndDate = null; existingAssignment.IsHome = true; existingAssignment.Role = _roleMappingRepository.FindMatchingRoleFromBusinessRole(activeEmployee.Role); } else { var assignment = new UserDepartment() { Employee = existing, Note = AutoGeneratedHomeAssignment, IsHome = true, Department = salesforceDepartment, StartDate = monthBegin, SlidingPayout = 100, Role = _roleMappingRepository.FindMatchingRoleFromBusinessRole(activeEmployee.Role) }; existing.Departments.Add(assignment); } homeUserDepartment.IsHome = false; homeUserDepartment.EndDate = nextMonthBegin.Subtract(TimeSpan.FromDays(1)); } else { homeUserDepartment.Role = _roleMappingRepository.FindMatchingRoleFromBusinessRole(activeEmployee.Role); } existing.HomeDepartment = activeEmployee.OrgUnit; existing.BaseRole = activeEmployee.Role; existing.FullName = activeEmployee.DisplayName; existing.ManagerKey = activeEmployee.ManagerKey; existing.Role = _roleMappingRepository.FindMatchingRoleFromBusinessRole(activeEmployee.Role); foreach (var userDepartment in existing.Departments?.Where(department => !department.IsHome && string.Equals(department.Note, AutoGeneratedHomeAssignment, StringComparison.InvariantCultureIgnoreCase))) { userDepartment.Note = ""; } return existing; } private async Task SyncDayForceEmployeeNew(ActiveEmployeeResult activeEmployee) { var newEmployee = new AppUser { Role = _roleMappingRepository.FindMatchingRoleFromBusinessRole(activeEmployee.Role), AdEmail = activeEmployee.LoginName, EmployeeId = activeEmployee.EmployeeNumber, HomeDepartment = activeEmployee.OrgUnit, BaseRole = activeEmployee.Role, FullName = activeEmployee.DisplayName, ManagerKey = activeEmployee.ManagerKey, Departments = new List() }; newEmployee.Departments.Add(await GenerateNewEmployeeHomeDepartment(newEmployee, activeEmployee)); await SaveUserAsync(newEmployee); return newEmployee; } private async Task GenerateNewEmployeeHomeDepartment(AppUser appUser, ActiveEmployeeResult activeEmployee) { var hireDate = activeEmployee.DetermineHireDate(); var assignment = new UserDepartment() { Employee = appUser, Note = AutoGeneratedHomeAssignment, IsHome = true, Department = await LookupDepartment(activeEmployee), StartDate = new DateTime(hireDate.Year, hireDate.Month, 1), SlidingPayout = 100, Role = _roleMappingRepository.FindMatchingRoleFromBusinessRole(activeEmployee.Role) }; return assignment; } public async Task SyncDayForceEmployee(ActiveEmployeeResult activeEmployee) { var existing = await GetUserByEmployeeIdAsync(activeEmployee.EmployeeNumber); if (existing != null) { existing = await SyncDayForceEmployeeExisting(existing, activeEmployee); await _unitOfWork.CompleteAsync(); } else { existing = await SyncDayForceEmployeeNew(activeEmployee); } return existing; } public async Task TerminateDayForceEmployee(TerminatedEmployeeResult terminatedEmployee) { var existingAppUser = await GetUserByEmployeeIdAsync(terminatedEmployee.EmployeeNumber); if (!(existingAppUser is { Terminated: null })) return null; var dataSyncSucceeded = await SyncUserIds(existingAppUser); if (!dataSyncSucceeded) { throw new Exception($"Failed to sync AppUser data for AppUserId: {existingAppUser.Id}"); } var monthBegin = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); var nextMonthBegin = monthBegin.AddMonths(1); var termDeptDate = nextMonthBegin.Subtract(TimeSpan.FromDays(1)); existingAppUser.AdditionalPermissions.Clear(); existingAppUser.Terminated = terminatedEmployee.TerminationDate; foreach (var department in existingAppUser.Departments) { if (department.EndDate == null || department.EndDate.Value.Date > termDeptDate.Date) { department.EndDate = termDeptDate; } } await _unitOfWork.CompleteAsync(); return existingAppUser; } private async Task SyncUserIds(AppUser appUser) { return await _applicationDbIntegrityService.SyncIdsWithDb(appUser); } public async Task> AssignDepartments( string employeeId, DepartmentAssignmentRequest assignmentRequest ) { var employee = await GetUserByEmployeeIdAsync(employeeId); if (employee == null) { throw new NotFoundException("Employee Not Found"); } var newAssignments = new List(); foreach (var departmentId in assignmentRequest.DepartmentId) { var department = await _departmentRepository.FindDepartmentByIdAsync(departmentId); if (department == null) { throw new NotFoundException("Department Not Found"); } var role = Role.GetRolesList() .FirstOrDefault(role => role.Name.Equals(assignmentRequest.Role)); if (role == null) { throw new BadDepartmentAssignmentException("Invalid Or Missing Role"); } if (assignmentRequest.SlidingPayout < 0 || assignmentRequest.SlidingPayout > UserDepartment.PayoutMax) { throw new BadDepartmentAssignmentException( $"Sliding Payout must be between 0 and {UserDepartment.PayoutMax}"); } var newAssignment = new UserDepartment() { Employee = employee, Department = department, Note = assignmentRequest.Note, StartDate = assignmentRequest.StartDate, EndDate = assignmentRequest.EndDate, Role = role, SlidingPayout = assignmentRequest.SlidingPayout }; foreach (var userDepartment in employee.Departments) { if (userDepartment.IsConflictingAssignment(newAssignment, out string message)) { throw new BadDepartmentAssignmentException(message); } } employee.Departments.Add(newAssignment); newAssignments.Add(newAssignment); } await _unitOfWork.CompleteAsync(); return newAssignments; } public async Task> UpdatePermissions(string employeeId, AdditionalPermissionRequest request) { await AssignPermissions(employeeId, request.AddPermCodes); var additional = await RemovePermissions(employeeId, request.RemovePermCodes); return additional; } public async Task> AssignPermissions(string employeeId, List permCodes) { var employee = await GetUserByEmployeeIdAsync(employeeId); if (employee == null) { throw new NotFoundException("Employee Not Found"); } var newPermissions = Permission.GetAllPermissions() .Where(permission => permCodes.Contains(permission.Code) && !employee.UserHasPermission(permission)) .ToList(); var newUserPermissions = newPermissions.Select(perm => new UserPermission() { Permission = perm, AppUser = employee }) .ToList(); employee.AdditionalPermissions ??= new List(); employee.AdditionalPermissions.AddRange(newUserPermissions); await _userPermissionHistoryRepository.SaveUserPermissionsInsertion(newUserPermissions); await _unitOfWork.CompleteAsync(); return employee.AdditionalPermissions.Select(x => x.Permission).ToList(); } public async Task> RemovePermissions(string employeeId, List permCodes) { var employee = await GetUserByEmployeeIdAsync(employeeId); if (employee == null) { throw new NotFoundException("Employee Not Found"); } var permissionsToRemove = permCodes.Select(requestCode => { return Permission.GetAllPermissions().SingleOrDefault(p => p.Code.Equals(requestCode)); }); var permissionsRemoved = employee.AdditionalPermissions.Where(existingPerm => permissionsToRemove.Contains(existingPerm.Permission)).ToList(); await _userPermissionHistoryRepository.SaveUserPermissionsDeletion(permissionsRemoved); employee.AdditionalPermissions = employee.AdditionalPermissions.Where(existingPerm => !permissionsToRemove.Contains(existingPerm.Permission)).ToList(); await _unitOfWork.CompleteAsync(); return employee.AdditionalPermissions.Select(x => x.Permission).ToList(); } public async Task UpdateDepartmentAssignment(DepartmentUpdateRequest request) { var existing = await GetUserByEmployeeIdAsync(request.EmployeeId); if (existing == null) { throw new NotFoundException("Employee not found."); } var assignment = existing.Departments.FirstOrDefault(x => x.Id == request.AssignmentId); if (assignment == null) { throw new NotFoundException("Assignment not found."); } var role = Role.GetRolesList().FirstOrDefault(role => role.Name.Equals(request.Role)); if (role == null) { throw new BadDepartmentAssignmentException("Invalid Or Missing Role"); } if (request.SlidingPayout is < 0 or > UserDepartment.PayoutMax) { throw new BadDepartmentAssignmentException($"Sliding Payout must be between 0 and {UserDepartment.PayoutMax}"); } await _userDepartmentHistoryRepository.SaveUserDepartmentUpdate(assignment, request); assignment.EndDate = request.EndDate; assignment.Role = role; assignment.SlidingPayout = request.SlidingPayout; await _unitOfWork.CompleteAsync(); return assignment; } public async Task> FindDepartmentAssignments(DepartmentPersonnelSearchRequest searchRequest) { return await _departmentRepository.FindDepartmentAssignments(searchRequest); } private async Task LookupDepartment(ActiveEmployeeResult employee) { var department = await _departmentRepository.FindOrCreateDepartment(employee); if (department == null) { throw new Exception("Couldn't get or create a department"); } return department; } public async Task TryFindSingleUserByName(UserNameSearchRequest searchRequest) { var userSearchRequest = new UserSearchRequest() { FirstName = searchRequest.FirstName, LastName = searchRequest.LastName, MiddleInit = searchRequest.MiddleInit, DnaOrgNbr = searchRequest.DnaOrgNbr }; var foundUsers = await _userRepository.SearchUsersAsync(userSearchRequest); if (foundUsers.Count < 1 && !string.IsNullOrWhiteSpace(userSearchRequest.MiddleInit)) { foundUsers = await _userRepository.SearchUsersAsync(new UserSearchRequest() { FirstName = userSearchRequest.FirstName, LastName = userSearchRequest.LastName, DnaOrgNbr = userSearchRequest.DnaOrgNbr }); } if (foundUsers.Count < 1) { foundUsers = await _userRepository.SearchUsersAsync(new UserSearchRequest() { FirstName = userSearchRequest.FirstName, LastName = userSearchRequest.LastName, }); } return foundUsers.Single(); } public async Task FindManager(AppUser appUser) { return await GetUserByEmployeeIdAsync(appUser.ManagerKey); } public async Task> FindDepartmentManagers(int departmentId) { var searchRequest = new DepartmentPersonnelSearchRequest { DepartmentId = departmentId, IncludeAllPersonnel = true, Month = DateTime.Today.Month, Year = DateTime.Today.Year }; var usersInDepartment = await _departmentRepository.FindDepartmentAssignments(searchRequest); var departmentManagers = usersInDepartment .Where(user => user.Departments .Any(deptAssignment => Role.DepartmentManagerRoles.Contains(deptAssignment.Role) && deptAssignment.DepartmentId == departmentId)) .ToList(); return departmentManagers; } public async Task UserHasAnyRequiredPermission(string appUserEmail, List allowedPermissions) { var user = await GetUserByAdEmailAsync(appUserEmail); if (user == null) { return false; } var hasPermission = false; foreach (var userDepartment in user.Departments) { var userDeptPermissions = userDepartment.Role.Permissions.Select(x => x.Code).ToList(); if (allowedPermissions.Any(p => userDeptPermissions.Contains(p))) { hasPermission = true; } } if (!hasPermission) { var userAdditionalPermissions = (user.AdditionalPermissions ?? new List()) .Select(x => x.Permission.Code) .ToList(); if (allowedPermissions.Any(p => userAdditionalPermissions.Contains(p))) { hasPermission = true; } } return hasPermission; } public async Task SyncActiveUserIds() { var allUsersSucceeded = true; try { var activeUsers = await _userRepository.GetAllUsersAsync(); foreach (var appUser in activeUsers.Where(x => x.Terminated == null)) { var userSyncSucceeded = await _applicationDbIntegrityService.SyncIdsWithDb(appUser); if (userSyncSucceeded) { continue; } allUsersSucceeded = false; Log.Error($"Failed to sync Ids for AppUser: {appUser.Id}"); } } catch (Exception ex) { Log.Error(ex, ex.Message); return false; } return allUsersSucceeded; } public static bool ValidateChangePermission(string currentEmployeeId, string targetEmployeeId) => currentEmployeeId == targetEmployeeId; public async Task UpdateEmployeeDetails(EmployeeDetailsUpdate employeeDetailsUpdate) { try { var existingAppUser = await GetUserByEmployeeIdAsync(employeeDetailsUpdate.OldEmployeeId); if (existingAppUser != null) { existingAppUser = await SyncEmployeeDetails(existingAppUser, employeeDetailsUpdate); await _unitOfWork.CompleteAsync(); return new EmployeeDetailsUpdateResult { AppUser = existingAppUser, UpdateMessage = $"Employee Details Update Succeeded", UpdatedSuccessfully = true }; } return new EmployeeDetailsUpdateResult { UpdateMessage = $"Employee Details Update Failed, No Existing Application User Found To Update" }; } catch (Exception ex) { Log.Error(ex, ex.Message); return new EmployeeDetailsUpdateResult { UpdateMessage = $"Employee Details Update Failed, {ex.Message}" }; } } public async Task SyncEmployeeDetails(AppUser appUser, EmployeeDetailsUpdate employeeDetailsUpdate) { appUser.AdEmail = employeeDetailsUpdate.NewEmail; appUser.EmployeeId = employeeDetailsUpdate.NewEmployeeId; appUser.FullName = employeeDetailsUpdate.NewFullName; var updateSyncSucceeded = await _applicationDbIntegrityService.SyncAppUserUpdate(appUser); if (!updateSyncSucceeded) { throw new Exception($"Failed to update AppUser details for AppUserId: {appUser.Id}"); } return appUser; } }