using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Mapster; using NPP.SmartSchedue.Api.Contracts.Domain.Time; using NPP.SmartSchedue.Api.Contracts.Services.Time; using NPP.SmartSchedue.Api.Contracts.Services.Time.Input; using NPP.SmartSchedue.Api.Contracts.Services.Time.Output; using NPP.SmartSchedue.Api.Contracts.Core.Enums; using NPP.SmartSchedue.Api.Contracts.Core.Extensions; using ZhonTai.Admin.Core.Dto; using ZhonTai.Admin.Services; using ZhonTai.DynamicApi; using ZhonTai.DynamicApi.Attributes; using Microsoft.AspNetCore.Mvc; using NPP.SmartSchedue.Api.Contracts; namespace NPP.SmartSchedue.Api.Services.Time; /// /// 班次不可用标记服务实现 /// 提供班次不可用数据的完整业务逻辑处理 /// [DynamicApi(Area = "app")] public class ShiftUnavailabilityService : BaseService, IShiftUnavailabilityService, IDynamicApi { private readonly IShiftUnavailabilityRepository _repository; private readonly IShiftRepository _shiftRepository; public ShiftUnavailabilityService( IShiftUnavailabilityRepository repository, IShiftRepository shiftRepository) { _repository = repository; _shiftRepository = shiftRepository; } #region 基础CRUD操作 /// /// 添加班次不可用标记 /// public async Task AddAsync(ShiftUnavailabilityAddInput input) { // 检查是否已存在相同的记录 var exists = await _repository.ExistsAsync(input.PersonnelId, input.Date, input.ShiftId); if (exists) { throw new Exception($"员工在 {input.Date:yyyy-MM-dd} 的该班次已有不可用标记"); } var entity = input.Adapt(); entity.ReasonType = (int)input.ReasonType; entity.CreatedTime = DateTime.Now; await _repository.InsertAsync(entity); return entity.Id; } /// /// 更新班次不可用标记 /// public async Task UpdateAsync(ShiftUnavailabilityUpdateInput input) { var entity = await _repository.GetAsync(input.Id); if (entity == null) { throw new Exception("记录不存在"); } // 更新可修改的字段 entity.ReasonType = (int)input.ReasonType; entity.Remark = input.Remark; entity.Priority = input.Priority; entity.EffectiveStartTime = input.EffectiveStartTime; entity.EffectiveEndTime = input.EffectiveEndTime; entity.ModifiedTime = DateTime.Now; return await _repository.UpdateAsync(entity) > 0; } /// /// 删除班次不可用标记 /// public async Task DeleteAsync(long id) { return await _repository.DeleteAsync(id) > 0; } /// /// 获取班次不可用标记详情 /// public async Task GetAsync(long id) { var entity = await _repository.GetAsync(id); if (entity == null) return null; var output = entity.Adapt(); // 设置枚举和扩展属性 output.ReasonType = ((UnavailabilityReasonType)entity.ReasonType); output.ReasonTypeName = entity.ReasonType.GetDisplayName(); output.Category = entity.ReasonType.GetCategory(); output.CategoryName = output.Category.ToString(); output.GridSymbol = entity.ReasonType.GetGridSymbol(); output.ColorClass = entity.ReasonType.GetColorClass(); return output; } /// /// 分页查询班次不可用标记 /// public async Task> GetPageAsync(PageInput input) { var query = _repository.Select; // 条件过滤 if (input.Filter.PersonnelId.HasValue) query = query.Where(x => x.PersonnelId == input.Filter.PersonnelId.Value); if (input.Filter.ShiftId.HasValue) query = query.Where(x => x.ShiftId == input.Filter.ShiftId.Value); if (input.Filter.StartDate.HasValue) query = query.Where(x => x.Date >= input.Filter.StartDate.Value.Date); if (input.Filter.EndDate.HasValue) query = query.Where(x => x.Date <= input.Filter.EndDate.Value.Date); if (input.Filter.ReasonType.HasValue) query = query.Where(x => x.ReasonType == (int)input.Filter.ReasonType.Value); if (input.Filter.Category.HasValue) { // 根据分组筛选对应的原因类型 var reasonTypes = GetReasonTypesByCategory(input.Filter.Category.Value); query = query.Where(x => reasonTypes.Contains(x.ReasonType)); } if (input.Filter.IsFromTemplate.HasValue) query = query.Where(x => x.IsFromTemplate == input.Filter.IsFromTemplate.Value); if (!string.IsNullOrWhiteSpace(input.Filter.Keyword)) query = query.Where(x => x.Remark.Contains(input.Filter.Keyword)); // 使用标准的FreeSql分页方式 var entities = await query .Count(out var total) .OrderByDescending(x => x.CreatedTime) .Page(input.CurrentPage, input.PageSize) .ToListAsync(); // 手动进行数据转换 var list = entities.Select(entity => { var output = entity.Adapt(); // 设置扩展属性 output.ReasonType = (UnavailabilityReasonType)entity.ReasonType; output.ReasonTypeName = entity.ReasonType.GetDisplayName(); output.Category = entity.ReasonType.GetCategory(); output.GridSymbol = entity.ReasonType.GetGridSymbol(); output.ColorClass = entity.ReasonType.GetColorClass(); output.DateDisplay = entity.Date.ToString("yyyy-MM-dd"); output.Weekday = GetWeekdayName(entity.Date); if (entity.SourceTemplateDate.HasValue) output.SourceTemplateDateDisplay = entity.SourceTemplateDate.Value.ToString("yyyy-MM-dd"); return output; }).ToList(); var data = new PageOutput() { List = list, Total = total }; return data; } #endregion #region 网格视图专用方法 /// /// 获取指定员工指定月份的网格数据 /// public async Task GetMonthlyGridDataAsync(long personnelId, DateTime month) { var monthStart = new DateTime(month.Year, month.Month, 1); var monthEnd = monthStart.AddMonths(1).AddDays(-1); // 一次性查询整月数据 var entities = await _repository.GetByPersonnelAndDateRangeAsync(personnelId, monthStart, monthEnd); // 获取所有启用的班次 var shifts = await _shiftRepository.Select .Where(x => x.IsEnabled) .ToListAsync(); // 构建网格数据结构 var grid = new Dictionary>(); var dates = new List(); // 初始化所有日期和班次的空单元格 for (var date = monthStart; date <= monthEnd; date = date.AddDays(1)) { dates.Add(date); grid[date] = new Dictionary(); foreach (var shift in shifts) { grid[date][shift.Id] = new CellData { IsUnavailable = false, DisplaySymbol = "", CssClass = "", TooltipText = $"{shift.Name} - 可用" }; } } // 填充不可用数据 foreach (var entity in entities) { if (grid.ContainsKey(entity.Date.Date) && grid[entity.Date.Date].ContainsKey(entity.ShiftId)) { var reasonType = (UnavailabilityReasonType)entity.ReasonType; grid[entity.Date.Date][entity.ShiftId] = new CellData { IsUnavailable = true, ReasonType = reasonType, DisplaySymbol = entity.ReasonType.GetGridSymbol(), CssClass = entity.ReasonType.GetColorClass(), Remark = entity.Remark, Priority = entity.Priority, IsFromTemplate = entity.IsFromTemplate, RecordId = entity.Id, TooltipText = $"{entity.ReasonType.GetDisplayName()}: {entity.Remark}" }; } } // 构建统计信息 var statistics = BuildGridStatistics(entities, shifts); return new GridDataOutput { Grid = grid, Dates = dates, Shifts = shifts.Select(s => new ShiftInfo { Id = s.Id, Name = s.Name, ShiftNumber = s.ShiftNumber, StartTime = s.StartTime, EndTime = s.EndTime, TimeRange = $"{s.StartTime:hh\\:mm}-{s.EndTime:hh\\:mm}", IsEnabled = s.IsEnabled }).ToList(), Statistics = statistics, MonthDisplay = month.ToString("yyyy年MM月") }; } /// /// 切换单元格状态 /// public async Task ToggleCellAsync(CellToggleInput input) { var exists = await _repository.ExistsAsync(input.PersonnelId, input.Date, input.ShiftId); if (exists) { // 删除现有记录 return await _repository.DeleteByConditionAsync(input.PersonnelId, input.Date, input.ShiftId) > 0; } else { // 创建新记录,使用默认的个人意愿原因 var entity = new ShiftUnavailabilityEntity { PersonnelId = input.PersonnelId, Date = input.Date, ShiftId = input.ShiftId, ReasonType = (int)UnavailabilityReasonType.PersonalPreference, Priority = 1, CreatedTime = DateTime.Now }; await _repository.InsertAsync(entity); return true; } } /// /// 设置单元格状态 /// public async Task SetCellAsync(CellSetInput input) { // 先删除现有记录 await _repository.DeleteByConditionAsync(input.PersonnelId, input.Date, input.ShiftId); // 如果指定了原因类型,则创建新记录 if (input.ReasonType.HasValue) { var entity = new ShiftUnavailabilityEntity { PersonnelId = input.PersonnelId, Date = input.Date, ShiftId = input.ShiftId, ReasonType = (int)input.ReasonType.Value, Remark = input.Remark, Priority = input.Priority, CreatedTime = DateTime.Now }; await _repository.InsertAsync(entity); } return true; } #endregion #region 智能排班集成方法 /// /// 获取指定日期和班次的不可用员工ID列表 /// public async Task> GetUnavailablePersonnelAsync(DateTime date, long shiftId) { return await _repository.GetUnavailablePersonnelIdsAsync(date, shiftId); } /// /// 检查人员班次的意愿 /// /// /// /// /// public async Task CheckUnavailablePersonnelByShift(DateTime date, long shiftId, long personnelId) { return await _repository.CheckUnavailablePersonnelByShiftAsync(date, shiftId, personnelId); } /// /// 获取指定日期所有班次的不可用员工分布 /// public async Task>> GetDailyUnavailablePersonnelAsync(DateTime date) { return await _repository.GetDailyUnavailablePersonnelAsync(date); } /// /// 批量获取日期范围内的不可用员工分布 /// public async Task>>> GetRangeUnavailablePersonnelAsync(DateTime startDate, DateTime endDate) { return await _repository.GetRangeUnavailablePersonnelAsync(startDate, endDate); } #endregion #region 私有辅助方法 /// /// 根据分组获取对应的原因类型列表 /// private static List GetReasonTypesByCategory(UnavailabilityCategory category) { var allReasonTypes = Enum.GetValues(); return allReasonTypes .Where(rt => rt.GetCategory() == category) .Select(rt => (int)rt) .ToList(); } /// /// 获取星期几的中文名称 /// private static string GetWeekdayName(DateTime date) { return date.DayOfWeek switch { DayOfWeek.Sunday => "周日", DayOfWeek.Monday => "周一", DayOfWeek.Tuesday => "周二", DayOfWeek.Wednesday => "周三", DayOfWeek.Thursday => "周四", DayOfWeek.Friday => "周五", DayOfWeek.Saturday => "周六", _ => "" }; } /// /// 构建网格统计信息 /// private static GridStatistics BuildGridStatistics(List entities, List shifts) { var stats = new GridStatistics { TotalCount = entities.Count, TemplateGeneratedCount = entities.Count(e => e.IsFromTemplate), ManualCreatedCount = entities.Count(e => !e.IsFromTemplate) }; // 按班次统计 stats.ShiftCounts = entities .GroupBy(e => e.ShiftId) .ToDictionary(g => g.Key, g => g.Count()); // 按原因类型统计 stats.ReasonTypeCounts = entities .GroupBy(e => (UnavailabilityReasonType)e.ReasonType) .ToDictionary(g => g.Key, g => g.Count()); // 按分组统计 stats.CategoryCounts = entities .GroupBy(e => ((UnavailabilityReasonType)e.ReasonType).GetCategory()) .ToDictionary(g => g.Key, g => g.Count()); return stats; } #endregion // 注意:批量操作方法和统计方法由于篇幅限制,将在后续实现 // 这里先提供基础CRUD和核心功能的完整实现 #region 待实现的方法(Phase 2 将完成) /// /// 复制模板到指定日期范围 /// 支持覆盖模式和保留模式 /// public async Task CopyTemplateAsync(TemplateCopyInput input) { var result = new BatchOperationResult { TotalCount = 0, SuccessCount = 0, FailedCount = 0, FailedItems = new List() }; try { // 获取模板日期的所有记录 var templateRecords = await _repository.GetByPersonnelAndDateRangeAsync( input.PersonnelId, input.SourceDate.Date, input.SourceDate.Date); if (!templateRecords.Any()) { result.Message = "模板日期没有找到任何不可用记录"; return result; } // 准备目标日期列表 var targetDates = input.TargetDates.Select(d => d.Date).ToList(); result.TotalCount = targetDates.Count * templateRecords.Count; // 使用事务确保数据一致性 using var uow = _repository.Orm.CreateUnitOfWork(); try { foreach (var targetDate in targetDates) { // 如果是覆盖模式,先删除目标日期的现有记录 if (input.OverwriteExisting) { await _repository.DeleteByConditionAsync(input.PersonnelId, targetDate, null); } // 复制每条模板记录到目标日期 foreach (var templateRecord in templateRecords) { try { // 检查是否已存在相同记录 var exists = await _repository.ExistsAsync(input.PersonnelId, targetDate, templateRecord.ShiftId); if (!exists || input.OverwriteExisting) { // 创建新记录 var newRecord = new ShiftUnavailabilityEntity { PersonnelId = input.PersonnelId, Date = targetDate, ShiftId = templateRecord.ShiftId, ReasonType = templateRecord.ReasonType, Remark = $"{templateRecord.Remark} (从{input.SourceDate:yyyy-MM-dd}复制)", Priority = templateRecord.Priority, EffectiveStartTime = templateRecord.EffectiveStartTime, EffectiveEndTime = templateRecord.EffectiveEndTime, IsFromTemplate = true, SourceTemplateDate = input.SourceDate.Date, CreatedTime = DateTime.Now }; if (exists && input.OverwriteExisting) { // 更新现有记录 await _repository.DeleteByConditionAsync(input.PersonnelId, targetDate, templateRecord.ShiftId); } await _repository.InsertAsync(newRecord); result.SuccessCount++; } else { // 记录已存在且不是覆盖模式 result.FailedCount++; result.FailedItems.Add(new BatchOperationFailedItem { Date = targetDate, ShiftId = templateRecord.ShiftId, Reason = "记录已存在" }); } } catch (Exception ex) { result.FailedCount++; result.FailedItems.Add(new BatchOperationFailedItem { Date = targetDate, ShiftId = templateRecord.ShiftId, Reason = $"操作失败: {ex.Message}" }); } } } uow.Commit(); result.IsSuccess = result.FailedCount == 0; result.Message = result.IsSuccess ? $"模板复制成功,共处理 {result.SuccessCount} 条记录" : $"模板复制完成,成功 {result.SuccessCount} 条,失败 {result.FailedCount} 条"; } catch (Exception) { uow.Rollback(); throw; } } catch (Exception ex) { result.IsSuccess = false; result.Message = $"模板复制操作失败: {ex.Message}"; result.FailedCount = result.TotalCount; } return result; } /// /// 设置周期性模式 /// 根据周模式在指定日期范围内批量生成记录 /// public async Task SetWeeklyPatternAsync(WeeklyPatternInput input) { var result = new BatchOperationResult { TotalCount = 0, SuccessCount = 0, FailedCount = 0, FailedItems = new List() }; try { // 验证输入参数 if (input.WeeklyPattern?.Any() != true) { result.Message = "周模式配置不能为空"; return result; } if (input.StartDate > input.EndDate) { result.Message = "开始日期不能大于结束日期"; return result; } var processedDates = new List(); // 遍历日期范围内的所有日期 for (var date = input.StartDate.Date; date <= input.EndDate.Date; date = date.AddDays(1)) { var dayOfWeek = (int)date.DayOfWeek; // 检查当前星期几是否在周模式配置中 if (input.WeeklyPattern.ContainsKey(dayOfWeek) && input.WeeklyPattern[dayOfWeek]?.Any() == true) { processedDates.Add(date); result.TotalCount += input.WeeklyPattern[dayOfWeek].Count; } } if (result.TotalCount == 0) { result.Message = "根据周模式配置,没有需要处理的日期"; return result; } // 使用事务确保数据一致性 using var uow = _repository.Orm.CreateUnitOfWork(); try { foreach (var date in processedDates) { var dayOfWeek = (int)date.DayOfWeek; var shiftIds = input.WeeklyPattern[dayOfWeek]; // 如果是覆盖模式,先删除该日期的现有记录 if (input.OverwriteExisting) { await _repository.DeleteByConditionAsync(input.PersonnelId, date, null); } // 为每个班次设置创建记录 foreach (var shiftId in shiftIds) { try { // 检查是否已存在记录 var exists = await _repository.ExistsAsync(input.PersonnelId, date, shiftId); if (!exists || input.OverwriteExisting) { var newRecord = new ShiftUnavailabilityEntity { PersonnelId = input.PersonnelId, Date = date, ShiftId = shiftId, ReasonType = (int)input.ReasonType, Remark = $"{input.Remark ?? "周期性设置"} ({GetWeekdayName(date)})", Priority = input.Priority, IsFromTemplate = true, SourceTemplateDate = input.StartDate, CreatedTime = DateTime.Now }; if (exists && input.OverwriteExisting) { await _repository.DeleteByConditionAsync(input.PersonnelId, date, shiftId); } await _repository.InsertAsync(newRecord); result.SuccessCount++; } else { // 记录已存在且不是覆盖模式 result.FailedCount++; result.FailedItems.Add(new BatchOperationFailedItem { Date = date, ShiftId = shiftId, Reason = "记录已存在" }); } } catch (Exception ex) { result.FailedCount++; result.FailedItems.Add(new BatchOperationFailedItem { Date = date, ShiftId = shiftId, Reason = $"操作失败: {ex.Message}" }); } } } uow.Commit(); result.IsSuccess = result.FailedCount == 0; result.Message = result.IsSuccess ? $"周期性设置成功,共处理 {result.SuccessCount} 条记录,涉及 {processedDates.Count} 个日期" : $"周期性设置完成,成功 {result.SuccessCount} 条,失败 {result.FailedCount} 条"; } catch (Exception) { uow.Rollback(); throw; } } catch (Exception ex) { result.IsSuccess = false; result.Message = $"周期性设置操作失败: {ex.Message}"; result.FailedCount = result.TotalCount; } return result; } /// /// 批量设置单元格状态 /// 支持多选单元格的批量操作 /// public async Task BatchSetCellsAsync(BatchCellEditInput input) { var result = new BatchOperationResult { TotalCount = 0, SuccessCount = 0, FailedCount = 0, FailedItems = new List() }; try { // 验证输入参数 if (input.CellOperations?.Any() != true) { result.Message = "单元格操作列表不能为空"; return result; } result.TotalCount = input.CellOperations.Count; // 使用事务确保数据一致性 using var uow = _repository.Orm.CreateUnitOfWork(); try { foreach (var operation in input.CellOperations) { try { switch (operation.Operation) { case BatchCellOperationType.Set: await HandleSetOperationAsync(operation, input.MarkAsTemplate, input.Remark); break; case BatchCellOperationType.Clear: await HandleClearOperationAsync(operation); break; case BatchCellOperationType.Toggle: await HandleToggleOperationAsync(operation); break; default: throw new ArgumentException($"不支持的操作类型: {operation.Operation}"); } result.SuccessCount++; } catch (Exception ex) { result.FailedCount++; result.FailedItems.Add(new BatchOperationFailedItem { Date = operation.Date, ShiftId = operation.ShiftId, Reason = $"操作失败: {ex.Message}" }); } } uow.Commit(); result.IsSuccess = result.FailedCount == 0; result.Message = result.IsSuccess ? $"批量设置成功,共处理 {result.SuccessCount} 个单元格" : $"批量设置完成,成功 {result.SuccessCount} 个,失败 {result.FailedCount} 个"; } catch (Exception) { uow.Rollback(); throw; } } catch (Exception ex) { result.IsSuccess = false; result.Message = $"批量单元格设置操作失败: {ex.Message}"; result.FailedCount = result.TotalCount; } return result; } /// /// 处理设置操作 /// private async Task HandleSetOperationAsync(BatchCellOperation operation, bool markAsTemplate, string batchRemark) { if (!operation.ReasonType.HasValue) { throw new ArgumentException("Set操作必须指定原因类型"); } // 先删除现有记录 await _repository.DeleteByConditionAsync(operation.PersonnelId, operation.Date, operation.ShiftId); // 创建新记录 var newRecord = new ShiftUnavailabilityEntity { PersonnelId = operation.PersonnelId, Date = operation.Date, ShiftId = operation.ShiftId, ReasonType = (int)operation.ReasonType.Value, Remark = operation.Remark ?? batchRemark ?? "批量设置", Priority = operation.Priority ?? 1, EffectiveStartTime = operation.EffectiveStartTime.HasValue ? TimeSpan.FromTicks(operation.EffectiveStartTime.Value.Ticks) : null, EffectiveEndTime = operation.EffectiveEndTime.HasValue ? TimeSpan.FromTicks(operation.EffectiveEndTime.Value.Ticks) : null, IsFromTemplate = markAsTemplate, SourceTemplateDate = markAsTemplate ? DateTime.Today : null, CreatedTime = DateTime.Now }; await _repository.InsertAsync(newRecord); } /// /// 处理清除操作 /// private async Task HandleClearOperationAsync(BatchCellOperation operation) { await _repository.DeleteByConditionAsync(operation.PersonnelId, operation.Date, operation.ShiftId); } /// /// 处理切换操作 /// private async Task HandleToggleOperationAsync(BatchCellOperation operation) { var exists = await _repository.ExistsAsync(operation.PersonnelId, operation.Date, operation.ShiftId); if (exists) { // 删除现有记录 await _repository.DeleteByConditionAsync(operation.PersonnelId, operation.Date, operation.ShiftId); } else { // 创建新记录,使用个人意愿作为默认原因 var newRecord = new ShiftUnavailabilityEntity { PersonnelId = operation.PersonnelId, Date = operation.Date, ShiftId = operation.ShiftId, ReasonType = (int)UnavailabilityReasonType.PersonalPreference, Remark = operation.Remark ?? "切换操作", Priority = operation.Priority ?? 1, EffectiveStartTime = operation.EffectiveStartTime.HasValue ? TimeSpan.FromTicks(operation.EffectiveStartTime.Value.Ticks) : null, EffectiveEndTime = operation.EffectiveEndTime.HasValue ? TimeSpan.FromTicks(operation.EffectiveEndTime.Value.Ticks) : null, CreatedTime = DateTime.Now }; await _repository.InsertAsync(newRecord); } } /// /// 获取指定员工的不可用统计数据 /// public async Task GetStatisticsAsync(long personnelId, DateTime startDate, DateTime endDate) { try { // 获取日期范围内的所有记录 var records = await _repository.GetByPersonnelAndDateRangeAsync(personnelId, startDate, endDate); // 获取所有班次信息 var shifts = await _shiftRepository.Select .Where(x => x.IsEnabled) .ToListAsync(); var totalDays = (endDate.Date - startDate.Date).Days + 1; var totalPossibleSlots = totalDays * shifts.Count; var statistics = new UnavailabilityStatistics { PersonnelId = personnelId, StartDate = startDate, EndDate = endDate, TotalDays = totalDays, TotalRecords = records.Count, TotalPossibleSlots = totalPossibleSlots, UnavailableRate = totalPossibleSlots > 0 ? (double)records.Count / totalPossibleSlots * 100 : 0 }; // 按班次统计 statistics.ShiftStatistics = shifts.Select(shift => new ShiftStatistics { ShiftId = shift.Id, ShiftName = shift.Name, Count = records.Count(r => r.ShiftId == shift.Id), Percentage = totalDays > 0 ? (decimal)((double)records.Count(r => r.ShiftId == shift.Id) / totalDays * 100) : 0 }).ToList(); // 按原因类型统计 statistics.ReasonTypeStatistics = records .GroupBy(r => (UnavailabilityReasonType)r.ReasonType) .Select(g => new ReasonTypeStatistics { ReasonType = g.Key, Count = g.Count(), Percentage = records.Count > 0 ? (decimal)((double)g.Count() / records.Count * 100) : 0, AffectedDays = g.Select(r => r.Date.Date).Distinct().Count() }).OrderByDescending(s => s.Count).ToList(); // 按分类统计 statistics.CategoryStatistics = records .GroupBy(r => ((UnavailabilityReasonType)r.ReasonType).GetCategory()) .Select(g => new CategoryStatistics { Category = g.Key, Count = g.Count(), Percentage = records.Count > 0 ? (decimal)((double)g.Count() / records.Count * 100) : 0, AffectedDays = g.Select(r => r.Date.Date).Distinct().Count(), ReasonTypes = g.Select(r => (UnavailabilityReasonType)r.ReasonType).Distinct().ToList() }).OrderByDescending(s => s.Count).ToList(); // 按星期几统计 statistics.WeekdayStatistics = records .GroupBy(r => r.Date.DayOfWeek) .Select(g => new WeekdayStatistics { DayOfWeek = g.Key, DayName = GetWeekdayName(DateTime.Today.AddDays((int)g.Key - (int)DateTime.Today.DayOfWeek)), Count = g.Count(), AffectedDates = g.Select(r => r.Date.Date).Distinct().Count() }).OrderBy(s => s.DayOfWeek).ToList(); // 模板生成统计 var templateRecords = records.Where(r => r.IsFromTemplate).ToList(); statistics.TemplateStatistics = new TemplateStatistics { TotalTemplateRecords = templateRecords.Count, TemplateGeneratedRate = records.Count > 0 ? (decimal)((double)templateRecords.Count / records.Count * 100) : 0, ManualCreatedRecords = records.Count - templateRecords.Count, ManualCreatedRate = records.Count > 0 ? (decimal)((double)(records.Count - templateRecords.Count) / records.Count * 100) : 0 }; // 月度趋势(如果时间范围超过一个月) if (totalDays > 31) { statistics.MonthlyTrend = records .GroupBy(r => new { Year = r.Date.Year, Month = r.Date.Month }) .Select(g => new MonthlyTrendItem { Year = g.Key.Year, Month = g.Key.Month, Count = g.Count(), UnavailableDays = g.Select(r => r.Date.Date).Distinct().Count(), MonthDisplay = $"{g.Key.Year}年{g.Key.Month:00}月" }).OrderBy(t => t.Year).ThenBy(t => t.Month).ToList(); } return statistics; } catch (Exception ex) { throw new Exception($"获取统计数据失败: {ex.Message}", ex); } } /// /// 获取团队成员的不可用报告 /// public async Task> GetTeamReportAsync(List personnelIds, DateTime month) { try { var monthStart = new DateTime(month.Year, month.Month, 1); var monthEnd = monthStart.AddMonths(1).AddDays(-1); var teamReports = new List(); // 并发获取每个成员的统计数据 var tasks = personnelIds.Select(async personnelId => { var statistics = await GetStatisticsAsync(personnelId, monthStart, monthEnd); return statistics; }); var results = await Task.WhenAll(tasks); teamReports.AddRange(results); // 对结果按不可用率排序 return teamReports.OrderByDescending(r => r.UnavailableRate).ToList(); } catch (Exception ex) { throw new Exception($"获取团队报告失败: {ex.Message}", ex); } } #endregion }