You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

薪资系统中按特殊班次拆分工时记录的优化方案咨询

排班工时拆分的性能优化方案

问题背景

现有薪资应用基于以下两个类实现排班工时管理:

public class PayrollLineitems
{
   public int PayrollLineItemID {get;set;} // 示例:1
   public int CaregiverID       {get;set;} // 示例:1
   public DateTime ScheduleDate {get;set;} // 示例:2022-01-01
   public DateTime StartTime    {get;set;} // 示例:2022-01-01 10:00 
   public DateTime EndTime      {get;set;} // 示例:2022-01-01 18:00
}

public class SpecialShifts
{
  public TimeSpan Start { get; set; } // 示例:08:00
  public TimeSpan End { get; set; } // 示例:17:00
  public string Days { get;set; } // 示例:6,7 → 6=周六,7=周日
}

需求为:将临床医生的排班记录按特殊班次规则拆分,符合特殊班次时段的工时标记为Shift(额外加薪10美元),其余标记为Regular。现有遍历匹配方法在SpecialShifts记录增多时性能低下,需优化拆分逻辑。

优化方案

1. 预编译特殊班次规则(空间换时间)

将SpecialShifts的规则转换为按星期几索引的时段集合,避免每次处理排班记录时遍历所有特殊班次:

  • 用Dictionary<int, List<(TimeSpan Start, TimeSpan End)>>存储,key为星期几(对应需求中的1-7,注意.NET默认DayOfWeek中周日为0,需转换为7),value为该星期几对应的所有特殊时段。
  • 初始化时一次性完成规则转换,同时处理跨天时段(如22:00到次日06:00),并对每个星期几的时段进行排序、合并重叠/连续区间,减少后续比对次数。

2. 高效时段交集匹配

针对单条排班记录的工时区间,结合预编译的规则,通过以下方式快速匹配特殊时段:

  • 直接根据排班日期的星期几取出对应的特殊时段集合,无需遍历所有SpecialShifts。
  • 对每个特殊时段,计算与当前工时区间的交集,拆分出Shift和Regular时段,处理完交集后更新当前工时的起始点,直到剩余时段处理完毕。

3. 策略模式封装逻辑

将拆分逻辑封装为策略类,便于后续规则扩展:

  • 定义IPayrollSplitStrategy接口,包含Split(PayrollLineitem item)方法,返回拆分后的工时条目(含类型和时段)。
  • 实现SpecialShiftSplitStrategy类,内部集成预编译规则和交集匹配逻辑。
  • 核心业务仅需调用策略的Split方法,无需关心具体实现细节,后续新增规则只需添加新策略类即可。

代码示例

预编译特殊班次规则

// 预编译特殊班次规则到字典
var specialShiftMap = new Dictionary<int, List<(TimeSpan Start, TimeSpan End)>>();
foreach (var shift in specialShifts)
{
    var dayNumbers = shift.Days.Split(',').Select(int.Parse).ToList();
    foreach (var day in dayNumbers)
    {
        if (!specialShiftMap.ContainsKey(day))
        {
            specialShiftMap[day] = new List<(TimeSpan, TimeSpan)>();
        }

        // 处理跨天时段
        if (shift.Start <= shift.End)
        {
            specialShiftMap[day].Add((shift.Start, shift.End));
        }
        else
        {
            // 拆分为当天到23:59:59,以及次日00:00到结束
            specialShiftMap[day].Add((shift.Start, TimeSpan.FromHours(23.9999)));
            var nextDay = day % 7 + 1;
            if (!specialShiftMap.ContainsKey(nextDay))
            {
                specialShiftMap[nextDay] = new List<(TimeSpan, TimeSpan)>();
            }
            specialShiftMap[nextDay].Add((TimeSpan.Zero, shift.End));
        }
    }
}

// 对每个星期几的时段排序并合并重叠区间
foreach (var kvp in specialShiftMap)
{
    var sortedShifts = kvp.Value.OrderBy(s => s.Start).ToList();
    var mergedShifts = new List<(TimeSpan Start, TimeSpan End)>();
    
    foreach (var shift in sortedShifts)
    {
        if (mergedShifts.Count == 0)
        {
            mergedShifts.Add(shift);
        }
        else
        {
            var last = mergedShifts.Last();
            if (shift.Start <= last.End)
            {
                // 合并重叠/连续区间
                mergedShifts.RemoveAt(mergedShifts.Count - 1);
                mergedShifts.Add((last.Start, TimeSpan.Max(last.End, shift.End)));
            }
            else
            {
                mergedShifts.Add(shift);
            }
        }
    }
    
    specialShiftMap[kvp.Key] = mergedShifts;
}

单条排班记录拆分逻辑

public List<(DateTime Start, DateTime End, string Type)> SplitPayrollItem(PayrollLineitem item, Dictionary<int, List<(TimeSpan Start, TimeSpan End)>> specialShiftMap)
{
    var result = new List<(DateTime, DateTime, string)>();
    var scheduleDate = item.ScheduleDate.Date;
    // 转换.NET DayOfWeek为需求的星期几(周日→7,其余直接转int)
    var mappedDay = (int)scheduleDate.DayOfWeek == 0 ? 7 : (int)scheduleDate.DayOfWeek;

    var currentStart = item.StartTime;
    var currentEnd = item.EndTime;

    // 无对应星期几的特殊班次,直接标记为Regular
    if (!specialShiftMap.TryGetValue(mappedDay, out var specialShifts))
    {
        result.Add((currentStart, currentEnd, "Regular"));
        return result;
    }

    foreach (var shift in specialShifts)
    {
        var shiftStart = scheduleDate.Add(shift.Start);
        var shiftEnd = scheduleDate.Add(shift.End);

        // 计算交集时段
        var overlapStart = DateTime.Max(currentStart, shiftStart);
        var overlapEnd = DateTime.Min(currentEnd, shiftEnd);

        if (overlapStart < overlapEnd)
        {
            // 添加交集前的Regular时段
            if (currentStart < overlapStart)
            {
                result.Add((currentStart, overlapStart, "Regular"));
            }
            // 添加Shift时段
            result.Add((overlapStart, overlapEnd, "Shift"));
            // 更新当前起始点
            currentStart = overlapEnd;
            
            if (currentStart >= currentEnd)
            {
                break;
            }
        }
    }

    // 添加剩余的Regular时段
    if (currentStart < currentEnd)
    {
        result.Add((currentStart, currentEnd, "Regular"));
    }

    return result;
}

内容的提问来源于stack exchange,提问作者Chethan SR

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 09:24:20