Files
yunzerwebsiteallinone/go/controllers/backend_oa_scheduling_template.go
2026-11-05 18:00:50 +08:00

198 lines
6.0 KiB
Go

package controllers
import (
"strconv"
"strings"
"server/models"
"github.com/beego/beego/v2/client/orm"
)
// 租户端排班模板:只读调用,不允许修改平台模板。
//
// 调用模板时会把模板的每日规则转成方案周期的 pattern 快照,并把模板里的
// 班次按「名称 + 上下班时间」匹配到租户自己的班次;匹配不到则自动创建,
// 这样租户拿到方案后就能像普通方案一样自由修改。
func schedTplTypeLabel(value int8) string {
return schedTplTypeText(value)
}
func (c *BackendOaSchedulingController) schedTplQuery() orm.QuerySeter {
return models.Orm.QueryTable(new(models.PlatformSchedTpl)).
Filter("delete_time__isnull", true)
}
func (c *BackendOaSchedulingController) schedTplDays(templateID uint64) []models.PlatformSchedTplDay {
rows := []models.PlatformSchedTplDay{}
_, err := models.Orm.QueryTable(new(models.PlatformSchedTplDay)).
Filter("template_id", templateID).
OrderBy("day_index").All(&rows)
if err != nil && err != orm.ErrNoRows {
return []models.PlatformSchedTplDay{}
}
return rows
}
func (c *BackendOaSchedulingController) schedTplByID(id uint64) (*models.PlatformSchedTpl, bool) {
var row models.PlatformSchedTpl
if err := c.schedTplQuery().Filter("id", id).One(&row); err != nil {
return nil, false
}
return &row, true
}
func schedTplView(row *models.PlatformSchedTpl, days []models.PlatformSchedTplDay) map[string]interface{} {
view := map[string]interface{}{
"id": row.ID,
"code": row.Code,
"name": row.Name,
"template_type": row.TemplateType,
"template_type_text": schedTplTypeLabel(row.TemplateType),
"align_mode": row.AlignMode,
"cycle_days": row.CycleDays,
"week_offset": row.WeekOffset,
"month_rest_days": row.MonthRestDays,
"month_rest_weekday": row.MonthRestWeekday,
"summary": row.Summary,
}
if days != nil {
view["days"] = days
}
return view
}
// TemplateList 租户端可调用的排班模板(只返回启用的模板)。
// GET /backend/oa/scheduling/templates?keyword=
func (c *BackendOaSchedulingController) TemplateList() {
if _, _, _, ok := c.scOperator(); !ok {
return
}
qs := c.schedTplQuery().Filter("status", 1)
if keyword := strings.TrimSpace(c.GetString("keyword")); keyword != "" {
qs = qs.Filter("name__icontains", keyword)
}
var rows []models.PlatformSchedTpl
if _, err := qs.OrderBy("sort", "id").All(&rows); err != nil && err != orm.ErrNoRows {
c.scFail(500, 500, "查询排班模板失败: "+err.Error())
return
}
list := make([]map[string]interface{}, 0, len(rows))
for i := range rows {
list = append(list, schedTplView(&rows[i], c.schedTplDays(rows[i].ID)))
}
c.scOK(map[string]interface{}{"list": list, "total": len(list)})
}
// TemplateApply 调用模板:返回可直接用于方案的周期规则,并把模板班次
// 匹配到租户班次(不存在时自动创建)。
// POST /backend/oa/scheduling/templates/apply body: {"template_id": 1}
func (c *BackendOaSchedulingController) TemplateApply() {
tid, _, _, ok := c.scOperator()
if !ok {
return
}
id := scUintValue(c.scBody(), "template_id")
if id == 0 {
if raw := strings.TrimSpace(c.GetString("template_id")); raw != "" {
id, _ = strconv.ParseUint(raw, 10, 64)
}
}
if id == 0 {
c.scFail(400, 400, "请选择排班模板")
return
}
tpl, found := c.schedTplByID(id)
if !found {
c.scFail(404, 404, "排班模板不存在")
return
}
if tpl.Status != 1 {
c.scFail(400, 400, "该模板已停用")
return
}
var shifts []models.OaShift
if _, err := c.shiftQuery(tid).OrderBy("sort", "id").All(&shifts); err != nil && err != orm.ErrNoRows {
c.scFail(500, 500, "查询班次失败: "+err.Error())
return
}
// 班次匹配索引:精确(名称+时间)→ 名称 → 兜底(默认班次 / 第一个可用班次)。
// 不强制时间一致,也不自动创建班次,由用户在方案里自行判断调整
shiftMap := make(map[string]*models.OaShift, len(shifts))
shiftNameMap := make(map[string]*models.OaShift, len(shifts))
var fallbackShift *models.OaShift
for i := range shifts {
shift := &shifts[i]
shiftMap[schedShiftKey(shift.Name, shift.WorkStart, shift.WorkEnd)] = shift
if _, ok := shiftNameMap[shift.Name]; !ok {
shiftNameMap[shift.Name] = shift
}
if fallbackShift == nil || (shift.IsDefault == 1 && fallbackShift.IsDefault != 1) {
fallbackShift = shift
}
}
patterns := []scPatternDay{}
matchedShifts := 0
for _, day := range c.schedTplDays(tpl.ID) {
if day.DayType != models.SchedTplDayWork {
patterns = append(patterns, scPatternDay{DayIndex: day.DayIndex, DayType: models.SchedTplDayRest})
continue
}
shift := shiftMap[schedShiftKey(day.ShiftName, day.WorkStart, day.WorkEnd)]
if shift == nil {
shift = shiftNameMap[strings.TrimSpace(day.ShiftName)]
}
if shift == nil {
shift = fallbackShift
}
if shift != nil {
matchedShifts++
}
item := scPatternDay{
DayIndex: day.DayIndex,
DayType: models.SchedTplDayWork,
ShiftName: day.ShiftName,
ShortName: day.ShiftShort,
Color: day.Color,
WorkStart: day.WorkStart,
WorkEnd: day.WorkEnd,
RestStart: day.RestStart,
RestEnd: day.RestEnd,
WorkHours: day.WorkHours,
}
if shift != nil {
item.ShiftID = shift.ID
item.ShiftName = shift.Name
item.ShortName = shift.ShortName
item.Color = shift.Color
item.WorkStart = shift.WorkStart
item.WorkEnd = shift.WorkEnd
item.RestStart = shift.RestStart
item.RestEnd = shift.RestEnd
item.WorkHours = shift.WorkHours
}
patterns = append(patterns, item)
}
if len(patterns) == 0 {
c.scFail(400, 400, "该模板没有配置排班规则")
return
}
c.scOK(map[string]interface{}{
"template": schedTplView(tpl, nil),
"pattern": patterns,
"days": patterns,
"matched_shift": matchedShifts,
})
}
func schedShiftKey(name, workStart, workEnd string) string {
return strings.TrimSpace(name) + "|" + strings.TrimSpace(workStart) + "|" + strings.TrimSpace(workEnd)
}