public SubordinatesWorkload GetPMSubordinatesWorkload(int projectManagerId, DateTimeRange dateRange, WorkloadPeriod workloadPeriodStep)
        {
            // TODO: Логика тяжеловесная и запутанная, надо подумать над возможными улучшениями
            var dateOnlyRange         = new DateTimeRange(dateRange.Begin.Date, dateRange.End.Date);
            var employees             = GetProjectManagerSubordinates(projectManagerId, dateOnlyRange);
            var membershipByEmployees = GetProjectMembershipByEmployees(dateOnlyRange, employees);
            var workhoursByDate       = ProductionCalendarSvc.GetWorkHoursByDate(dateOnlyRange);

            var projectComparer = new FuncBasedComparer <Project>((first, second) =>
            {
                if (first.EmployeePMID == projectManagerId && second.EmployeePMID != projectManagerId)
                {
                    return(-1);
                }
                if (first.EmployeePMID != projectManagerId && second.EmployeePMID == projectManagerId)
                {
                    return(1);
                }
                int res = string.Compare(first.FullName, second.FullName);
                if (res != 0)
                {
                    res = first.ID - second.ID;
                }
                return(res);
            });

            var workload = new SubordinatesWorkload();

            foreach (var employee in employees)
            {
                var employeeProjects = ProjectMembershipSvc.GetProjectsForEmployee(employee.ID, dateOnlyRange);
                employeeProjects = employeeProjects.OrderBy(e => e, projectComparer).ToList();
                var employeeProjectWorkload = new EmployeeProjectWorkload();
                employeeProjectWorkload.Employee = employee;
                foreach (var project in employeeProjects)
                {
                    if (!membershipByEmployees.TryGetValue(employee.ID, out var projectMemberships))
                    {
                        projectMemberships = new List <ProjectMember>();
                    }
                    var dailyWorkload = GetEmployeeDailyWorkload(project.ID, dateRange, projectMemberships, workhoursByDate);

                    var employeeWorkload = GetEmployeeWorkloadForDateRange(employee, dateRange, workloadPeriodStep, workhoursByDate, dailyWorkload);
                    var workloadRecord   = new EmployeeProjectWorkloadRecord();
                    workloadRecord.Project = project;
                    workloadRecord.WorkloadRecords.AddRange(employeeWorkload.WorkloadRecords);
                    employeeProjectWorkload.WorkloadRecords.Add(workloadRecord);
                }
                if (employeeProjectWorkload.WorkloadRecords.Count > 0)
                {
                    workload.SubordinateEmployeesWorkloads.Add(employeeProjectWorkload);
                }
            }
            return(workload);
        }
        public ProjectWorkload GetProjectWorkload(int projectId, DateTimeRange dateRange)
        {
            var dateOnlyRange         = new DateTimeRange(dateRange.Begin.Date, dateRange.End.Date);
            var projectEmployees      = ProjectMembershipSvc.GetEmployeesOnProject(projectId, dateOnlyRange);
            var membershipByEmployees = GetProjectMembershipByEmployees(dateOnlyRange, projectEmployees);
            var workhoursByDate       = ProductionCalendarSvc.GetWorkHoursByDate(dateOnlyRange);
            var projectWorkload       = new ProjectWorkload
            {
                ProjectId = projectId,
                DateFrom  = dateOnlyRange.Begin.Date,
                DateTo    = dateOnlyRange.End.Date
            };
            var employeeWorkloads = GetEmployeesWorkloadForDateRange(projectEmployees, dateOnlyRange, WorkloadPeriod.Week, membershipByEmployees, workhoursByDate, projectId);

            projectWorkload.EmployeeWorkloads.AddRange(employeeWorkloads);
            projectWorkload.TotalHours = projectWorkload.EmployeeWorkloads.Sum(ew => ew.WorkloadRecords.Sum(wr => wr.CurrentProjectHours));
            return(projectWorkload);
        }
        private IList <Employee> GetProjectManagerSubordinates(int projectManagerId, DateTimeRange dateOnlyRange)
        {
            var projects     = ProjectMembershipSvc.GetProjectsForManager(projectManagerId, dateOnlyRange);
            var comparer     = new FuncBasedEqualityComparer <Employee>((first, second) => first.ID == second.ID, e => e.ID.GetHashCode());
            var employeesSet = new HashSet <Employee>(comparer);

            foreach (var project in projects)
            {
                var projectEmployees = ProjectMembershipSvc.GetEmployeesOnProject(project.ID, dateOnlyRange);
                foreach (var employee in projectEmployees)
                {
                    employeesSet.Add(employee);
                }
            }
            var employees = employeesSet.OrderBy(e => e.FullName).ThenBy(e => e.ID).ToList();

            return(employees);
        }
        private Dictionary <int, List <ProjectMember> > GetProjectMembershipByEmployees(DateTimeRange dateRange, IList <Employee> employees)
        {
            var projectMemberships    = ProjectMembershipSvc.GetProjectMembershipForEmployees(dateRange, employees);
            var membershipByEmployees = new Dictionary <int, List <ProjectMember> >(employees.Count);

            foreach (var projectMember in projectMemberships)
            {
                // Членство в проекте в роле КАМ не учитывается в подсчете загрузки
                if (projectMember.ProjectRole != null && projectMember.ProjectRole.RoleType == ProjectRoleType.CAM)
                {
                    continue;
                }

                List <ProjectMember> list;
                if (!membershipByEmployees.TryGetValue(projectMember.Employee.ID, out list))
                {
                    list = new List <ProjectMember>();
                    membershipByEmployees.Add(projectMember.Employee.ID, list);
                }
                list.Add(projectMember);
            }

            return(membershipByEmployees);
        }