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) }