91 lines
2.9 KiB
Go
91 lines
2.9 KiB
Go
package duty_schedule
|
|
|
|
import (
|
|
"errors"
|
|
"sort"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
var (
|
|
ErrInvalid = errors.New("值班排班信息不符合要求")
|
|
ErrNotFound = errors.New("值班组或排班不存在")
|
|
ErrConflict = errors.New("数据已被其他人员更新,请刷新后重试")
|
|
ErrCoverage = errors.New("周排班存在空档或重叠")
|
|
)
|
|
|
|
type GroupInput struct {
|
|
Name string `json:"name"`
|
|
Timezone string `json:"timezone"`
|
|
ExpectedVersion int `json:"expectedVersion"`
|
|
}
|
|
type MemberInput struct {
|
|
ContactID string `json:"contactId"`
|
|
Role string `json:"role"`
|
|
}
|
|
type SlotInput struct {
|
|
Weekday int `json:"weekday"`
|
|
StartMinute int `json:"startMinute"`
|
|
EndMinute int `json:"endMinute"`
|
|
PrimaryContactID string `json:"primaryContactId"`
|
|
BackupContactID string `json:"backupContactId"`
|
|
}
|
|
type ScheduleInput struct {
|
|
EffectiveFrom time.Time `json:"effectiveFrom"`
|
|
EffectiveTo *time.Time `json:"effectiveTo"`
|
|
Slots []SlotInput `json:"slots"`
|
|
}
|
|
type OverrideInput struct {
|
|
OriginalContactID string `json:"originalContactId"`
|
|
ReplacementContactID string `json:"replacementContactId"`
|
|
StartsAt time.Time `json:"startsAt"`
|
|
EndsAt time.Time `json:"endsAt"`
|
|
Reason string `json:"reason"`
|
|
}
|
|
|
|
func normalizeGroup(input GroupInput) (GroupInput, error) {
|
|
input.Name = strings.TrimSpace(input.Name)
|
|
input.Timezone = strings.TrimSpace(input.Timezone)
|
|
if input.Name == "" || len([]rune(input.Name)) > 128 {
|
|
return GroupInput{}, ErrInvalid
|
|
}
|
|
if _, err := time.LoadLocation(input.Timezone); err != nil {
|
|
return GroupInput{}, ErrInvalid
|
|
}
|
|
return input, nil
|
|
}
|
|
func validateSlots(slots []SlotInput) error {
|
|
if len(slots) == 0 {
|
|
return ErrCoverage
|
|
}
|
|
byDay := map[int][]SlotInput{}
|
|
for _, slot := range slots {
|
|
if slot.Weekday < 0 || slot.Weekday > 6 || slot.StartMinute < 0 || slot.EndMinute > 1440 || slot.StartMinute >= slot.EndMinute || slot.PrimaryContactID == "" || slot.BackupContactID == "" || slot.PrimaryContactID == slot.BackupContactID {
|
|
return ErrInvalid
|
|
}
|
|
byDay[slot.Weekday] = append(byDay[slot.Weekday], slot)
|
|
}
|
|
for day := 0; day < 7; day++ {
|
|
daySlots := byDay[day]
|
|
sort.Slice(daySlots, func(i, j int) bool { return daySlots[i].StartMinute < daySlots[j].StartMinute })
|
|
cursor := 0
|
|
for _, slot := range daySlots {
|
|
if slot.StartMinute != cursor {
|
|
return ErrCoverage
|
|
}
|
|
cursor = slot.EndMinute
|
|
}
|
|
if cursor != 1440 {
|
|
return ErrCoverage
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
func normalizeOverride(input OverrideInput) (OverrideInput, error) {
|
|
input.Reason = strings.TrimSpace(input.Reason)
|
|
if input.OriginalContactID == "" || input.ReplacementContactID == "" || input.OriginalContactID == input.ReplacementContactID || input.StartsAt.IsZero() || !input.EndsAt.After(input.StartsAt) || input.Reason == "" || len([]rune(input.Reason)) > 256 {
|
|
return OverrideInput{}, ErrInvalid
|
|
}
|
|
return input, nil
|
|
}
|