paiban/NPP.SmartSchedue.Api/Services/Time/ShiftUnavailabilityService.cs
Asoka.Wang 0a2e2d9b18 123
2025-09-02 18:52:35 +08:00

1005 lines
38 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;
/// <summary>
/// 班次不可用标记服务实现
/// 提供班次不可用数据的完整业务逻辑处理
/// </summary>
[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操作
/// <summary>
/// 添加班次不可用标记
/// </summary>
public async Task<long> 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<ShiftUnavailabilityEntity>();
entity.ReasonType = (int)input.ReasonType;
entity.CreatedTime = DateTime.Now;
await _repository.InsertAsync(entity);
return entity.Id;
}
/// <summary>
/// 更新班次不可用标记
/// </summary>
public async Task<bool> 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;
}
/// <summary>
/// 删除班次不可用标记
/// </summary>
public async Task<bool> DeleteAsync(long id)
{
return await _repository.DeleteAsync(id) > 0;
}
/// <summary>
/// 获取班次不可用标记详情
/// </summary>
public async Task<ShiftUnavailabilityGetOutput> GetAsync(long id)
{
var entity = await _repository.GetAsync(id);
if (entity == null) return null;
var output = entity.Adapt<ShiftUnavailabilityGetOutput>();
// 设置枚举和扩展属性
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;
}
/// <summary>
/// 分页查询班次不可用标记
/// </summary>
public async Task<PageOutput<ShiftUnavailabilityGetPageOutput>> GetPageAsync(PageInput<ShiftUnavailabilityGetPageInput> 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<ShiftUnavailabilityGetPageOutput>();
// 设置扩展属性
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<ShiftUnavailabilityGetPageOutput>()
{
List = list,
Total = total
};
return data;
}
#endregion
#region
/// <summary>
/// 获取指定员工指定月份的网格数据
/// </summary>
public async Task<GridDataOutput> 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<DateTime, Dictionary<long, CellData>>();
var dates = new List<DateTime>();
// 初始化所有日期和班次的空单元格
for (var date = monthStart; date <= monthEnd; date = date.AddDays(1))
{
dates.Add(date);
grid[date] = new Dictionary<long, CellData>();
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月")
};
}
/// <summary>
/// 切换单元格状态
/// </summary>
public async Task<bool> 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;
}
}
/// <summary>
/// 设置单元格状态
/// </summary>
public async Task<bool> 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
/// <summary>
/// 获取指定日期和班次的不可用员工ID列表
/// </summary>
public async Task<List<long>> GetUnavailablePersonnelAsync(DateTime date, long shiftId)
{
return await _repository.GetUnavailablePersonnelIdsAsync(date, shiftId);
}
/// <summary>
/// 检查人员班次的意愿
/// </summary>
/// <param name="date"></param>
/// <param name="shiftId"></param>
/// <param name="personnelId"></param>
/// <returns></returns>
public async Task<bool> CheckUnavailablePersonnelByShift(DateTime date, long shiftId, long personnelId)
{
return await _repository.CheckUnavailablePersonnelByShiftAsync(date, shiftId, personnelId);
}
/// <summary>
/// 获取指定日期所有班次的不可用员工分布
/// </summary>
public async Task<Dictionary<long, List<long>>> GetDailyUnavailablePersonnelAsync(DateTime date)
{
return await _repository.GetDailyUnavailablePersonnelAsync(date);
}
/// <summary>
/// 批量获取日期范围内的不可用员工分布
/// </summary>
public async Task<Dictionary<DateTime, Dictionary<long, List<long>>>> GetRangeUnavailablePersonnelAsync(DateTime startDate, DateTime endDate)
{
return await _repository.GetRangeUnavailablePersonnelAsync(startDate, endDate);
}
#endregion
#region
/// <summary>
/// 根据分组获取对应的原因类型列表
/// </summary>
private static List<int> GetReasonTypesByCategory(UnavailabilityCategory category)
{
var allReasonTypes = Enum.GetValues<UnavailabilityReasonType>();
return allReasonTypes
.Where(rt => rt.GetCategory() == category)
.Select(rt => (int)rt)
.ToList();
}
/// <summary>
/// 获取星期几的中文名称
/// </summary>
private static string GetWeekdayName(DateTime date)
{
return date.DayOfWeek switch
{
DayOfWeek.Sunday => "周日",
DayOfWeek.Monday => "周一",
DayOfWeek.Tuesday => "周二",
DayOfWeek.Wednesday => "周三",
DayOfWeek.Thursday => "周四",
DayOfWeek.Friday => "周五",
DayOfWeek.Saturday => "周六",
_ => ""
};
}
/// <summary>
/// 构建网格统计信息
/// </summary>
private static GridStatistics BuildGridStatistics(List<ShiftUnavailabilityEntity> entities, List<ShiftEntity> 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
/// <summary>
/// 复制模板到指定日期范围
/// 支持覆盖模式和保留模式
/// </summary>
public async Task<BatchOperationResult> CopyTemplateAsync(TemplateCopyInput input)
{
var result = new BatchOperationResult
{
TotalCount = 0,
SuccessCount = 0,
FailedCount = 0,
FailedItems = new List<BatchOperationFailedItem>()
};
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;
}
/// <summary>
/// 设置周期性模式
/// 根据周模式在指定日期范围内批量生成记录
/// </summary>
public async Task<BatchOperationResult> SetWeeklyPatternAsync(WeeklyPatternInput input)
{
var result = new BatchOperationResult
{
TotalCount = 0,
SuccessCount = 0,
FailedCount = 0,
FailedItems = new List<BatchOperationFailedItem>()
};
try
{
// 验证输入参数
if (input.WeeklyPattern?.Any() != true)
{
result.Message = "周模式配置不能为空";
return result;
}
if (input.StartDate > input.EndDate)
{
result.Message = "开始日期不能大于结束日期";
return result;
}
var processedDates = new List<DateTime>();
// 遍历日期范围内的所有日期
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;
}
/// <summary>
/// 批量设置单元格状态
/// 支持多选单元格的批量操作
/// </summary>
public async Task<BatchOperationResult> BatchSetCellsAsync(BatchCellEditInput input)
{
var result = new BatchOperationResult
{
TotalCount = 0,
SuccessCount = 0,
FailedCount = 0,
FailedItems = new List<BatchOperationFailedItem>()
};
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;
}
/// <summary>
/// 处理设置操作
/// </summary>
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);
}
/// <summary>
/// 处理清除操作
/// </summary>
private async Task HandleClearOperationAsync(BatchCellOperation operation)
{
await _repository.DeleteByConditionAsync(operation.PersonnelId, operation.Date, operation.ShiftId);
}
/// <summary>
/// 处理切换操作
/// </summary>
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);
}
}
/// <summary>
/// 获取指定员工的不可用统计数据
/// </summary>
public async Task<UnavailabilityStatistics> 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);
}
}
/// <summary>
/// 获取团队成员的不可用报告
/// </summary>
public async Task<List<UnavailabilityStatistics>> GetTeamReportAsync(List<long> personnelIds, DateTime month)
{
try
{
var monthStart = new DateTime(month.Year, month.Month, 1);
var monthEnd = monthStart.AddMonths(1).AddDays(-1);
var teamReports = new List<UnavailabilityStatistics>();
// 并发获取每个成员的统计数据
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
}