using LCU.API.Internal.Operations.IncentiveApi.Models.Dayforce; using LCU.API.Internal.Operations.IncentiveApi.Models.Payout; using LCU.API.Internal.Operations.IncentiveApiLib.Repositories; using LCU.API.Internal.Operations.IncentiveApi.Services; using LCU.Library.Internal.IncentiveApiModels.Entities; using LCU.Library.Internal.IncentiveApiModels.Common; 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.Payout; public class PayoutService : IPayoutService { private readonly IUserRepository _userRepository; private readonly IPayoutRepository _payoutRepository; private readonly IAccountingRepository _accountingRepository; private readonly IUnitOfWork _unitOfWork; private readonly IPayoutCalculateService _payoutCalculateService; private readonly IActivityService _activityService; private readonly ProcessResult _payoutIncentiveProcessResult; private const int MonthsBack = -2; public PayoutService(IUserRepository userRepository, IPayoutRepository payoutRepository, IUnitOfWork unitOfWork, IPayoutCalculateService payoutCalculateService, IAccountingRepository accountingRepository, IActivityService activityService) { _userRepository = userRepository; _payoutRepository = payoutRepository; _unitOfWork = unitOfWork; _payoutCalculateService = payoutCalculateService; _accountingRepository = accountingRepository; _activityService = activityService; _payoutIncentiveProcessResult = new ProcessResult(); } public async Task RunMonthlyIncentivePayouts(PayoutIncentiveRequest request) { try { Log.Information("Running Monthly Incentive Payout Process"); var result = await CreateMonthlyIncentivePayouts(request); Log.Information("Successfully Processed Monthly Incentive Payouts."); await SafeQueueReturnResponse.TryQueueResponse(request, _payoutIncentiveProcessResult.Success, _payoutIncentiveProcessResult.MessageStack, nameof(PayoutService)); } catch (Exception exception) { Log.Fatal(exception, "There was an Unhandled error with Monthly Incentive Payout Process"); var monthsBack = request?.MonthsBack.ToString() ?? "unknown"; var message = $"There was an Unhandled error with Monthly Incentive Payout Process from {monthsBack} months back from today's date. Exception Message: {exception.Message}."; _payoutIncentiveProcessResult.MessageStack.Add(message); _payoutIncentiveProcessResult.Success = false; await SafeQueueReturnResponse.TryQueueResponse(request, _payoutIncentiveProcessResult.Success, _payoutIncentiveProcessResult.MessageStack, nameof(PayoutService)); } } public async Task> CreateMonthlyIncentivePayouts(PayoutIncentiveRequest request) { var period = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); period = request.MonthsBack == 0 ? period.AddMonths(MonthsBack) : period.AddMonths(request.MonthsBack); var range = new MonthRange(period); _activityService.CancelPendingTransfersBetween(range).GetAwaiter().GetResult(); var users = await _userRepository.GetAllUsersToPayoutAsync(range); var allNewPayouts = new List(); IReadOnlyCollection combinedPayouts; foreach (var incentiveUser in users) { try { combinedPayouts = await CreateCombinedDepartmentPayouts(incentiveUser, range); if (!combinedPayouts.Any()) continue; await _payoutRepository.SavePayoutLineItemRangeAsync(combinedPayouts); allNewPayouts.AddRange(combinedPayouts); } catch (Exception ex) { Log.Fatal(ex, $"Employee {incentiveUser.EmployeeId} - {incentiveUser.FullName} experienced a failed payout calculation!"); } } await _unitOfWork.CompleteAsync(); return allNewPayouts; } public async Task CreateMonthlyAccountingPayouts(MonthRange range) { var existing = await RetrieveMonthlyAccountingPayouts(range); if (existing != null) { return existing; } var users = await _userRepository.GetAllUsersToPayoutAsync(range); var allNewPayouts = new List(); var deptNames = new Dictionary(); foreach (var incentiveUser in users) { try { var departments = incentiveUser.ActiveDepartmentsForRange(range); foreach (var userDepartment in departments) { if (!deptNames.ContainsKey(userDepartment.Department.Id.Value)) { deptNames.Add(userDepartment.Department.Id.Value, userDepartment.Department.DepartmentName); } var newPlan = await _payoutCalculateService.BuildDepartmentPayoutView(incentiveUser, userDepartment, range); allNewPayouts.AddRange(newPlan.CommissionPayouts); allNewPayouts.AddRange(newPlan.GoalPayouts); } } catch (Exception ex) { Log.Fatal(ex, $"Employee {incentiveUser.EmployeeId} - {incentiveUser.FullName} experienced a failed accounting calculation!"); } } var summary = new AccountingSummary(range.StartTime.Date); summary.Apply(allNewPayouts, deptNames); await _accountingRepository.SaveAccountingSummaryAsync(summary); await _unitOfWork.CompleteAsync(); return summary; } public async Task RetrieveMonthlyAccountingPayouts(MonthRange range) { return await _accountingRepository.GetAccountingSummaryAsync(range.StartTime); } public async Task> BuildUserDepartmentsPayoutView(AppUser employee, MonthRange range) { var list = new List(); var departments = employee.ActiveDepartmentsForRange(range); foreach (var userDepartment in departments) { var newPlan = await _payoutCalculateService.BuildDepartmentPayoutView(employee, userDepartment, range); list.Add(newPlan); } return list; } public async Task> RetrieveMonthlyIncentivePayouts(DateTime period) { var payoutsRaw = await _payoutRepository.GetPayoutLineItemsForPeriodAsync(period); var payouts = new Dictionary(); foreach (var lineItem in payoutsRaw) { if (payouts.ContainsKey(lineItem.EmployeeId)) { payouts[lineItem.EmployeeId].PayoutTotal += lineItem.PayoutTotal; } else { var employee = await _userRepository.FindUserByEmployeeIdAsync(lineItem.EmployeeId); payouts[lineItem.EmployeeId] = new PayoutEntry() { Code = "Incentive", EmployeeId = lineItem.EmployeeId, PayoutTotal = lineItem.PayoutTotal, EmployeeName = employee.FullName }; } } return payouts.Values; } public async Task> CreateCombinedDepartmentPayouts(AppUser incentiveAppUser, MonthRange range) { var combinedPayouts = new List(); var existing = await _payoutRepository.GetEmployeePayoutsForPeriod(incentiveAppUser, range.StartTime); if (existing.Any()) return combinedPayouts; var payouts = await BuildUserDepartmentsPayoutView(incentiveAppUser, range); foreach (var departmentPayout in payouts) { combinedPayouts.AddRange(departmentPayout.CommissionPayouts); combinedPayouts.AddRange(departmentPayout.GoalPayouts); } return combinedPayouts; } public async Task> RetrieveCombinedDepartmentPayouts(AppUser incentiveAppUser, MonthRange range) { var existing = await _payoutRepository.GetEmployeePayoutsForPeriod(incentiveAppUser, range.StartTime); if (existing.Any()) return existing; return await CreateCombinedDepartmentPayouts(incentiveAppUser, range); } public async Task RetrieveUserMonthlyIncentivePayout(AppUser appUser, DateTime period) { var payoutsRaw = await _payoutRepository.GetEmployeePayoutsForPeriod(appUser, period); var payout = new PayoutEntry() { Code = "Incentive", EmployeeId = appUser.EmployeeId, PayoutTotal = 0, EmployeeName = appUser.FullName }; foreach (var lineItem in payoutsRaw) { payout.PayoutTotal += lineItem.PayoutTotal; } return payout; } public async Task CreateTerminatedEmployeePayouts(AppUser appUser, AppUser manager) { var firstOfThisMonth = new DateTime(DateTime.Today.Year, DateTime.Today.Month, 1); var rangeThisMonth = new MonthRange(firstOfThisMonth); var rangeLastMonth = new MonthRange(firstOfThisMonth.AddMonths(-1)); var payouts = await CreateCombinedDepartmentPayouts(appUser, rangeThisMonth); if (payouts.Any()) { await _payoutRepository.SavePayoutLineItemRangeAsync(payouts); } payouts = await CreateCombinedDepartmentPayouts(appUser, rangeLastMonth); if (payouts.Any()) { await _payoutRepository.SavePayoutLineItemRangeAsync(payouts); } await _unitOfWork.CompleteAsync(); var payoutThisMonth = await RetrieveUserMonthlyIncentivePayout(appUser, rangeThisMonth.StartTime); var payoutLastMonth = await RetrieveUserMonthlyIncentivePayout(appUser, rangeLastMonth.StartTime); return new TerminatedEmployeeReceipt(appUser, payoutLastMonth, payoutThisMonth, manager); } }