using Microsoft.Extensions.Localization; using Microsoft.Extensions.Logging; using SmallSoftwareContracts.BusinessLogicsContracts; using SmallSoftwareContracts.DataModels; using SmallSoftwareContracts.Exceptions; using SmallSoftwareContracts.Extensions; using SmallSoftwareContracts.Infrastructure; using SmallSoftwareContracts.Infrastructure.PostConfigurations; using SmallSoftwareContracts.Resources; using SmallSoftwareContracts.StoragesContracts; namespace SmallSoftwareBusinessLogic.Implementations; internal class SalaryBusinessLogicContract(ISalaryStorageContract salaryStorageContract, IRequestStorageContract requestStorageContract, IPostStorageContract postStorageContract, IWorkerStorageContract workerStorageContract, IStringLocalizer localizer, ILogger logger, IConfigurationSalary сonfiguration) : ISalaryBusinessLogicContract { private readonly ILogger _logger = logger; private readonly ISalaryStorageContract _salaryStorageContract = salaryStorageContract; private readonly IRequestStorageContract _requestStorageContract = requestStorageContract; private readonly IPostStorageContract _postStorageContract = postStorageContract; private readonly IWorkerStorageContract _workerStorageContract = workerStorageContract; private readonly IConfigurationSalary _salaryConfiguration = сonfiguration; private readonly Lock _lockObject = new(); private readonly IStringLocalizer _localizer = localizer; public List GetAllSalariesByPeriod(DateTime fromDate, DateTime toDate) { _logger.LogInformation("GetAllSalaries params: {fromDate}, {toDate}", fromDate, toDate); if (fromDate.IsDateNotOlder(toDate)) { throw new IncorrectDatesException(fromDate, toDate, _localizer); } return _salaryStorageContract.GetList(fromDate, toDate) ?? throw new NullListException(); } public List GetAllSalariesByPeriodByWorker(DateTime fromDate, DateTime toDate, string workerId) { if (fromDate.IsDateNotOlder(toDate)) { throw new IncorrectDatesException(fromDate, toDate, _localizer); } if (workerId.IsEmpty()) { throw new ArgumentNullException(nameof(workerId)); } if (!workerId.IsGuid()) { throw new ValidationException(string.Format(localizer["ValidationExceptionMessageNotAId"], "WorkerId")); } _logger.LogInformation("GetAllSalaries params: {fromDate}, {toDate}, { workerId} ", fromDate, toDate, workerId); return _salaryStorageContract.GetList(fromDate, toDate, workerId) ?? throw new NullListException(); } public void CalculateSalaryByMonth(DateTime date) { _logger.LogInformation("CalculateSalaryByMounth: {date}", date); var startDate = new DateTime(date.Year, date.Month, 1); var finishDate = new DateTime(date.Year, date.Month, DateTime.DaysInMonth(date.Year, date.Month)); var workers = _workerStorageContract.GetList() ?? throw new NullListException(); foreach (var worker in workers) { var requests = _requestStorageContract.GetList(startDate, finishDate, workerId: worker.Id) ?? throw new NullListException(); var post = _postStorageContract.GetElementById(worker.PostId) ?? throw new NullListException(); var salary = worker.ConfigurationModel switch { null => 0, CashierPostConfiguration cpc => CalculateSalaryForCashier(requests, startDate, finishDate, cpc), SupervisorPostConfiguration spc => CalculateSalaryForSupervisor(startDate, finishDate, spc), PostConfiguration pc => pc.Rate, }; _logger.LogDebug("The employee {workerId} was paid a salary of {salary}", worker.Id, salary); _salaryStorageContract.AddElement(new SalaryDataModel(worker.Id, finishDate, salary)); } } private double CalculateSalaryForCashier(List requests, DateTime startDate, DateTime finishDate, CashierPostConfiguration config) { var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = _salaryConfiguration.MaxConcurrentThreads }; double calcPercent = 0.0; var dates = new List(); for (var date = startDate; date < finishDate; date = date.AddDays(1)) { dates.Add(date); } Parallel.ForEach(dates, parallelOptions, date => { var requestsInDay = requests.Where(x => x.RequestDate >= date && x.RequestDate < date.AddDays(1)).ToArray(); if (requestsInDay.Length > 0) { double dailySum = requestsInDay.Sum(x => x.Sum); double dailyAverage = dailySum / requestsInDay.Length; double dailyPercent = dailyAverage * config.SalePercent; lock (_lockObject) { calcPercent += dailyPercent; } } }); double bonus = 0; try { bonus = requests .AsParallel() .WithDegreeOfParallelism(_salaryConfiguration.MaxConcurrentThreads) .Where(x => x.Sum > _salaryConfiguration.ExtraSaleSum) .Sum(x => x.Sum * config.BonusForExtraSales); } catch (AggregateException agEx) { foreach (var ex in agEx.InnerExceptions) { _logger.LogError(ex, "Error calculating bonus in cashier payroll"); } return 0; } return config.Rate + calcPercent + bonus; } private double CalculateSalaryForSupervisor(DateTime startDate, DateTime finishDate, SupervisorPostConfiguration config) { try { return config.Rate + config.PersonalCountTrendPremium * _workerStorageContract.GetWorkerTrend(startDate, finishDate); } catch (Exception ex) { _logger.LogError(ex, "Error in the supervisor payroll process"); return 0; } } }