优化排班功能
This commit is contained in:
1 parent
8acbe732ff
commit
8ba15d87b3
13 files changed
+2463
-565
No files matched your search
@@ -8,6 +8,7 @@ import (
|
||||
"time"
|
||||
|
||||
"server/models"
|
||||
"server/pkg/holiday"
|
||||
)
|
||||
|
||||
// scPatternDay 排班方案周期规则的每日快照。
|
||||
@@ -212,6 +213,12 @@ func scPlanWorkRules(plan *models.OaSchedulePlan, fallback *models.OaShift, date
|
||||
}
|
||||
}
|
||||
|
||||
// 周期类方案:构造基准周期之外的顺延上下文(节假日 / 调休修正 + 星期节奏)
|
||||
var rollExt *scRollExt
|
||||
if plan.AlignMode == models.SchedTplAlignRoll || plan.AlignMode == models.SchedTplAlignWeek {
|
||||
rollExt = scBuildRollExt(pattern, start, dates)
|
||||
}
|
||||
|
||||
result := make(map[string]scPatternDay, len(dates))
|
||||
workdays := make([]string, 0, len(dates))
|
||||
for _, date := range dates {
|
||||
@@ -219,7 +226,7 @@ func scPlanWorkRules(plan *models.OaSchedulePlan, fallback *models.OaShift, date
|
||||
if !valid {
|
||||
continue
|
||||
}
|
||||
rule, work := scRuleForDate(plan, rules, fallback, start, day, weekdays)
|
||||
rule, work := scRuleForDate(plan, rules, fallback, start, day, weekdays, rollExt)
|
||||
if !work {
|
||||
continue
|
||||
}
|
||||
@@ -229,9 +236,109 @@ func scPlanWorkRules(plan *models.OaSchedulePlan, fallback *models.OaShift, date
|
||||
return result, workdays, ""
|
||||
}
|
||||
|
||||
// scRollExt 周期滚动方案(AlignMode=2,按月涂排)在基准周期之外的顺延上下文。
|
||||
//
|
||||
// 涂刷基准月里的节假日安排属于「基准月的特殊规则」,不应该跟着日序号平移到
|
||||
// 后续月份——否则 10 月的国庆假期会错位落到 11 月的普通工作日上。周期之外的
|
||||
// 推算规则:
|
||||
// 1. 目标日期是法定节假日 → 休息;
|
||||
// 2. 目标日期是调休上班日 → 上班(取涂刷里的班次);
|
||||
// 3. 该星期在涂刷周期内呈现严格 休/班 交替(如大小周的周六)→ 按周序奇偶交替顺延;
|
||||
// 4. 其余按涂刷月的星期节奏顺延(同星期几在周期内的多数状态)。
|
||||
type scRollExt struct {
|
||||
weekdayRules [8]scPatternDay // 下标 1-7(周一~周日):该星期的顺延规则
|
||||
altRules [8]bool // 该星期是否隔周交替(严格 休/班 交替)
|
||||
altEven [8]scPatternDay // 交替星期:周序为偶数时的规则
|
||||
altOdd [8]scPatternDay // 交替星期:周序为奇数时的规则
|
||||
defaultWork scPatternDay // 涂刷里的首个上班规则,供调休上班日使用
|
||||
holidays map[string]int8 // 目标日期 → holiday.Type(0 调休上班 / 1 法定放假)
|
||||
}
|
||||
|
||||
// scBuildRollExt 从涂刷周期构造顺延上下文。星期节奏取多数状态(work 数 - rest 数
|
||||
// ≥ 0 视为上班,班次取该星期首个上班规则);若某星期在周期内严格 休/班 交替,
|
||||
// 记录交替规则,顺延按周序奇偶保持交替(周序锚点 = start 所在周的周一)。
|
||||
// 节假日数据按目标区间涉及的年份拉取,拉取失败只是少了节假日修正,不阻断发布。
|
||||
func scBuildRollExt(pattern []scPatternDay, start time.Time, dates []string) *scRollExt {
|
||||
ext := &scRollExt{holidays: map[string]int8{}}
|
||||
workCount := [8]int{}
|
||||
shiftBy := [8]scPatternDay{}
|
||||
type weekSeqItem struct {
|
||||
week int
|
||||
work bool
|
||||
item scPatternDay
|
||||
}
|
||||
byWeekday := [8][]weekSeqItem{}
|
||||
startMonday := start.AddDate(0, 0, -scWeekdayOffset(start))
|
||||
for _, item := range pattern {
|
||||
day := start.AddDate(0, 0, item.DayIndex)
|
||||
w := scWeekdayOffset(day) + 1
|
||||
if item.DayType == models.SchedTplDayWork {
|
||||
workCount[w]++
|
||||
if shiftBy[w].ShiftID == 0 {
|
||||
shiftBy[w] = item
|
||||
}
|
||||
if ext.defaultWork.ShiftID == 0 {
|
||||
ext.defaultWork = item
|
||||
}
|
||||
} else {
|
||||
workCount[w]--
|
||||
}
|
||||
weekSeq := int(day.AddDate(0, 0, -scWeekdayOffset(day)).Sub(startMonday).Hours()/24) / 7
|
||||
byWeekday[w] = append(byWeekday[w], weekSeqItem{week: weekSeq, work: item.DayType == models.SchedTplDayWork, item: item})
|
||||
}
|
||||
for w := 1; w <= 7; w++ {
|
||||
if workCount[w] >= 0 && shiftBy[w].ShiftID != 0 {
|
||||
ext.weekdayRules[w] = shiftBy[w]
|
||||
} else {
|
||||
ext.weekdayRules[w] = scPatternDay{DayIndex: w - 1, DayType: models.SchedTplDayRest}
|
||||
}
|
||||
// 隔周交替检测:该星期在涂刷周期内严格 休/班 交替(如大小周的周六,
|
||||
// 一周上班一周休息),顺延时按周序奇偶保持交替,而不是取多数
|
||||
list := byWeekday[w]
|
||||
if len(list) < 2 {
|
||||
continue
|
||||
}
|
||||
alternating := true
|
||||
for i := 1; i < len(list); i++ {
|
||||
if list[i].work == list[i-1].work {
|
||||
alternating = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if !alternating {
|
||||
continue
|
||||
}
|
||||
ext.altRules[w] = true
|
||||
if list[0].week%2 == 0 {
|
||||
ext.altEven[w] = list[0].item
|
||||
ext.altOdd[w] = list[1].item
|
||||
} else {
|
||||
ext.altOdd[w] = list[0].item
|
||||
ext.altEven[w] = list[1].item
|
||||
}
|
||||
}
|
||||
years := map[int]bool{}
|
||||
for _, date := range dates {
|
||||
if y := holidayYear(date); y > 0 {
|
||||
years[y] = true
|
||||
}
|
||||
}
|
||||
for y := range years {
|
||||
items, _, err := holiday.FetchYear(y)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, d := range items {
|
||||
ext.holidays[d.Date] = d.Type
|
||||
}
|
||||
}
|
||||
return ext
|
||||
}
|
||||
|
||||
// scRuleForDate 推算某一天命中的规则;第二个返回值表示当天是否上班。
|
||||
// ext 是周期滚动方案(AlignMode=2)在基准周期之外的顺延上下文,可为 nil。
|
||||
func scRuleForDate(plan *models.OaSchedulePlan, rules map[int]scPatternDay, fallback *models.OaShift,
|
||||
start, day time.Time, weekdays map[int]bool) (scPatternDay, bool) {
|
||||
start, day time.Time, weekdays map[int]bool, ext *scRollExt) (scPatternDay, bool) {
|
||||
switch plan.AlignMode {
|
||||
case models.SchedTplAlignWeek:
|
||||
weeks := plan.CycleDays / 7
|
||||
@@ -241,6 +348,18 @@ func scRuleForDate(plan *models.OaSchedulePlan, rules map[int]scPatternDay, fall
|
||||
startMonday := start.AddDate(0, 0, -scWeekdayOffset(start))
|
||||
curMonday := day.AddDate(0, 0, -scWeekdayOffset(day))
|
||||
diffWeeks := int(curMonday.Sub(startMonday).Hours()/24) / 7
|
||||
// 首个周期内原样回放;周期之外目标月的节假日 / 调休优先修正
|
||||
//(大小周等模板按自然周循环,周序永远交替不串位)
|
||||
if ext != nil && diffWeeks >= weeks {
|
||||
if hType, ok := ext.holidays[day.Format("2006-01-02")]; ok {
|
||||
if hType == holiday.TypeHoliday {
|
||||
return scPatternDay{}, false
|
||||
}
|
||||
if hType == holiday.TypeWorkday && ext.defaultWork.ShiftID != 0 {
|
||||
return ext.defaultWork, true
|
||||
}
|
||||
}
|
||||
}
|
||||
weekIndex := scMod(diffWeeks+int(plan.WeekOffset), weeks)
|
||||
return scRuleOfDay(rules, weekIndex*7+scWeekdayOffset(day))
|
||||
|
||||
@@ -250,6 +369,41 @@ func scRuleForDate(plan *models.OaSchedulePlan, rules map[int]scPatternDay, fall
|
||||
cycle = 1
|
||||
}
|
||||
diffDays := int(day.Sub(start).Hours() / 24)
|
||||
// 基准周期内:按涂刷结果原样回放(涂刷月自己的节假日 / 周末已在保存时处理)
|
||||
if diffDays < cycle || ext == nil {
|
||||
return scRuleOfDay(rules, scMod(diffDays, cycle))
|
||||
}
|
||||
// 周期之外:先看目标日的节假日 / 调休特殊规则
|
||||
if hType, ok := ext.holidays[day.Format("2006-01-02")]; ok {
|
||||
if hType == holiday.TypeHoliday {
|
||||
return scPatternDay{}, false
|
||||
}
|
||||
if hType == holiday.TypeWorkday && ext.defaultWork.ShiftID != 0 {
|
||||
return ext.defaultWork, true
|
||||
}
|
||||
}
|
||||
// 整月涂刷方案(周期=开始月天数):按星期节奏顺延,规避节假日 / 周末错位;
|
||||
// 其他滚动周期(三班倒 / 做一休一等模板原生周期):保持相位纯滚动
|
||||
daysInStartMonth := time.Date(start.Year(), start.Month()+1, 0, 0, 0, 0, 0, start.Location()).Day()
|
||||
if cycle == daysInStartMonth {
|
||||
w := scWeekdayOffset(day) + 1
|
||||
rule := ext.weekdayRules[w]
|
||||
// 隔周交替的星期(如大小周的周六):按周序奇偶保持交替
|
||||
if ext.altRules[w] {
|
||||
startMonday := start.AddDate(0, 0, -scWeekdayOffset(start))
|
||||
curMonday := day.AddDate(0, 0, -scWeekdayOffset(day))
|
||||
weekSeq := int(curMonday.Sub(startMonday).Hours()/24) / 7
|
||||
if weekSeq%2 == 0 {
|
||||
rule = ext.altEven[w]
|
||||
} else {
|
||||
rule = ext.altOdd[w]
|
||||
}
|
||||
}
|
||||
if rule.DayType != models.SchedTplDayWork {
|
||||
return scPatternDay{}, false
|
||||
}
|
||||
return rule, true
|
||||
}
|
||||
return scRuleOfDay(rules, scMod(diffDays, cycle))
|
||||
|
||||
case models.SchedTplAlignMonth:
|
||||
|
||||
Reference in new issue
Block a user