using LCU.API.Internal.Operations.IncentiveApi.Services.Payout; using LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; using LCU.API.Internal.Operations.IncentiveApiLib.Services; using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; using LCU.Library.Internal.IncentiveApiModels.Entities; using LCU.Library.Internal.IncentiveApiModels.Results; using LCU.API.Internal.Operations.IncentiveApi.Services; using LCU.API.Internal.Operations.IncentiveApi.Services.MicroApiServices; using LCU.API.Internal.Operations.IncentiveApi.Configuration; using LCU.Library.Internal.IncentiveApiModels.Requests; using LCU.Library.Internal.ApiResponse.Services; using ProcessResult = LCU.Library.Internal.IncentiveApiModels.Results.ProcessResult; namespace LCU.API.Internal.Operations.IncentiveApi.Services.Dayforce; public class DayForceSyncService : IDayForceSyncService { private readonly IUnitOfWork _unitOfWork; private readonly IUserService _userService; private readonly INotificationApiService _notificationService; private readonly IPayoutService _payoutService; private readonly IDayForceClientService _dayForceClientService; private readonly ISettingsService _settingsService; private readonly DayForceConfiguration _dayForceConfig; private readonly ProcessResult _dayforceSyncProcessResult; public DayForceSyncService( IUnitOfWork unitOfWork, IUserService userService, INotificationApiService notificationService, IPayoutService payoutService, IDayForceClientService dayForceClientService, ISettingsService settingsService, DayForceConfiguration dayForceConfig ) { _unitOfWork = unitOfWork; _userService = userService; _notificationService = notificationService; _payoutService = payoutService; _dayForceClientService = dayForceClientService; _settingsService = settingsService; _dayForceConfig = dayForceConfig; _dayforceSyncProcessResult = new ProcessResult(); } public async Task SyncDayForceUsers(DayforceProcessRequest request) { try { Log.Information("Starting Dayforce Sync Process for Active Users."); if (request.MissingRoleNotification == null) { request.MissingRoleNotification = _dayForceConfig.DayForceManualReportRecipients; } if (request.TerminatedEmployeeNotification == null) { request.TerminatedEmployeeNotification = _dayForceConfig.DayForceManualReportRecipients; } var activeUsersResult = await SyncActiveDayForceUsers(request.MissingRoleNotification); Log.Information("Completed Dayforce Sync Process for Active Users."); _dayforceSyncProcessResult.MessageStack.Add($"Dayforce Sync Process for Active Users complete with {activeUsersResult.Count()}"); Log.Information("Starting Dayforce Sync Process for Terminated Users."); var terminatedUsersResult = await SyncTerminatedDayForceUsers(request.TerminatedEmployeeNotification); Log.Information("Completed Dayforce Sync Process for Terminated Users."); _dayforceSyncProcessResult.MessageStack.Add($"Dayforce Sync Process for Terminated Users complete with {terminatedUsersResult.Count()}"); await SafeQueueReturnResponse.TryQueueResponse(request, _dayforceSyncProcessResult.Success, _dayforceSyncProcessResult.MessageStack, nameof(DayForceSyncService)); } catch ( Exception exception ) { Log.Fatal(exception, "There was an Unhandled error Executing Dayforce Sync Process"); var message = $"There was an issue with handling the Dayforce Sync Service Process Exception Message: {exception.Message}."; _dayforceSyncProcessResult.MessageStack.Add(message); _dayforceSyncProcessResult.Success = false; await SafeQueueReturnResponse.TryQueueResponse(request, _dayforceSyncProcessResult.Success, _dayforceSyncProcessResult.MessageStack, nameof(DayForceSyncService)); } } public async Task> SyncActiveDayForceUsers(List recipients) { var report = await _dayForceClientService.GetReportData(_dayForceConfig.DayForceActiveUserReport); var results = new List(); if (report.Rows == null) { throw new Exception("Dayforce returned invalid active report"); } foreach (var reportRow in report.Rows) { var receipt = new ActiveEmployeeReceipt(reportRow); results.Add(receipt); try { var employee = new ActiveEmployeeResult(reportRow); receipt.EmployeeRow = employee; if (!employee.CanProcess()) { Log.Error("Cannot process dayforce employee. {reportRow}", reportRow.Values); receipt.Status = ActiveEmployeeReceiptStatus.MISSING_DATA; continue; } var appUser = await _userService.SyncDayForceEmployee(employee); receipt.AppUser = appUser; receipt.Status = ActiveEmployeeReceiptStatus.SUCCESS; if (appUser.Departments.Any(x => x.Role == Role.MISSING_ROLE)) { receipt.Status = ActiveEmployeeReceiptStatus.MISSING_ROLE; } } catch (Exception ex) { Log.Error(ex, "Failed to process dayforce employee. {reportRow}", reportRow.Values); receipt.Status = ActiveEmployeeReceiptStatus.EXCEPTION; } } var nonExistentRoleAssignments = results .Where(x => x.Status == ActiveEmployeeReceiptStatus.MISSING_ROLE) .Select(x => x.AppUser) .ToList(); if (nonExistentRoleAssignments.Any()) { var newRoleMappings = nonExistentRoleAssignments.Select(x => x.BaseRole).ToList().Distinct(); await _notificationService.SendMissingRoleNotification(nonExistentRoleAssignments, recipients); await _settingsService.SaveNewUnknownRoleMappingRangeAsync(newRoleMappings); } return results; } public async Task> SyncTerminatedDayForceUsers(List recipients) { var report = await _dayForceClientService.GetReportData(_dayForceConfig.DayForceTerminatedUserReport); var terminated = new List(); if (report.Rows == null) { throw new Exception("Dayforce returned invalid termination report"); } foreach (var reportRow in report.Rows) { try { var employee = new TerminatedEmployeeResult(reportRow); var user = await _userService.TerminateDayForceEmployee(employee); if (user != null) { var manager = await _userService.FindManager(user); var receipt = await _payoutService.CreateTerminatedEmployeePayouts(user, manager); terminated.Add(receipt); } } catch (Exception ex) { Log.Error(ex, "Failed to term dayforce employee. {reportRow}", reportRow.Values); } } if (terminated.Any()) { await _notificationService.SendTerminatedEmployeeNotification(terminated, recipients); } return terminated; } public async Task UpdateEmployeeDetails(EmployeeDetailsUpdate employeeDetailsUpdate) { var currentAppUser = await _userService.GetUserByAdEmailAsync(employeeDetailsUpdate.OldEmail); if (currentAppUser == null) { return new EmployeeDetailsUpdateResult { UpdateMessage = $"Employee Details Update Failed, No Current Users Returned From Database" }; } if (!string.IsNullOrWhiteSpace(employeeDetailsUpdate.InvalidUpdateData())) { return new EmployeeDetailsUpdateResult { AppUser = currentAppUser, UpdateMessage = $"Employee Details Update Failed, {employeeDetailsUpdate.InvalidUpdateData()}" }; } var existingUserForNewEmail = await _userService.GetActiveUserByAdEmailAsync(employeeDetailsUpdate.NewEmail); if (existingUserForNewEmail != null && !existingUserForNewEmail.AdEmail.Equals(currentAppUser.AdEmail, StringComparison.InvariantCultureIgnoreCase)) { return new EmployeeDetailsUpdateResult { AppUser = currentAppUser, UpdateMessage = $"Employee Details Update Failed, Another Active User Already Exists With Email: {employeeDetailsUpdate.NewEmail}" }; } var existingUserForNewEmployeeId = await _userService.GetActiveUserByEmployeeIdAsync(employeeDetailsUpdate.NewEmployeeId); if (existingUserForNewEmployeeId != null && !existingUserForNewEmployeeId.EmployeeId.Equals(currentAppUser.EmployeeId, StringComparison.InvariantCultureIgnoreCase)) { return new EmployeeDetailsUpdateResult { AppUser = currentAppUser, UpdateMessage = $"Employee Details Update Failed, Another Active User Already Exists With EmployeeId: {employeeDetailsUpdate.NewEmployeeId}" }; } var hcmUsersMatched = await MatchingHcmUserCount(employeeDetailsUpdate); if (hcmUsersMatched != 1) { return new EmployeeDetailsUpdateResult { AppUser = currentAppUser, UpdateMessage = $"Employee Details Update Failed, {hcmUsersMatched} Users Returned From HCM System Matching New Details" }; } return await _userService.UpdateEmployeeDetails(employeeDetailsUpdate); } public async Task> GetMatchingHcmUsers(string email = "", string employeeId = "", string fullName = "") { if ( string.IsNullOrWhiteSpace(email) && string.IsNullOrWhiteSpace(employeeId) && string.IsNullOrWhiteSpace(fullName) ) { return new List(); } var dayForceUsers = await GetAllHcmUsers(); return dayForceUsers.Where(x => x.EmployeeNumber.ToString().Equals(employeeId, StringComparison.InvariantCultureIgnoreCase) || x.DisplayName.ToString().Equals(fullName, StringComparison.InvariantCultureIgnoreCase) || x.LoginName.ToString().Equals(email, StringComparison.InvariantCultureIgnoreCase) ) .ToList(); } private async Task> GetAllHcmUsers() { var report = await _dayForceClientService.GetReportData(_dayForceConfig.DayForceActiveUserReport); var results = new List(); if (report.Rows == null) { throw new Exception("Dayforce returned invalid active report"); } foreach (var reportRow in report.Rows) { try { var employee = new ActiveEmployeeResult(reportRow); results.Add(employee); } catch (Exception ex) { Log.Error(ex, "Failed to parse dayforce employee. {reportRow}", reportRow.Values); } } return results.Where(x => x.CanProcess()).OrderBy(x => x.DisplayName).ToList(); } private async Task MatchingHcmUserCount(EmployeeDetailsUpdate employeeDetailsUpdate) { var dayForceUsers = await GetAllHcmUsers(); var matchingDayforceUsers = dayForceUsers.Where(x => x.EmployeeNumber.ToString().Equals(employeeDetailsUpdate.NewEmployeeId, StringComparison.InvariantCultureIgnoreCase) && x.DisplayName.ToString().Equals(employeeDetailsUpdate.NewFullName, StringComparison.InvariantCultureIgnoreCase) && x.LoginName.ToString().Equals(employeeDetailsUpdate.NewEmail, StringComparison.InvariantCultureIgnoreCase) ) .ToList(); return matchingDayforceUsers.Count; } }