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
}