package controllers import ( "strconv" "strings" "server/models" "github.com/beego/beego/v2/client/orm" ) // schedulingEmployee 排班看板/方案预览里的员工视图。 // Department 保留组织 ID 字符串,DepartmentName 供前端直接展示。 type schedulingEmployee struct { ID uint `json:"id"` Name string `json:"name"` Account string `json:"account"` Department string `json:"department"` DepartmentName string `json:"department_name"` Position string `json:"position"` Phone string `json:"phone"` AccountStatus int8 `json:"account_status"` } // scOrgIndex 租户组织架构的内存索引,用于把“部门/小组”展开成部门子树, // 以及给员工补齐部门名称。一次请求只加载一次,避免 N+1 查询。 type scOrgIndex struct { rows map[uint64]models.BackendOrganization children map[uint64][]uint64 names map[uint64]string } func (c *BackendOaSchedulingController) scLoadOrgIndex(tid uint64) *scOrgIndex { index := &scOrgIndex{ rows: map[uint64]models.BackendOrganization{}, children: map[uint64][]uint64{}, names: map[uint64]string{}, } var rows []models.BackendOrganization _, err := models.Orm.QueryTable(new(models.BackendOrganization)). Filter("tid", tid). Filter("delete_time__isnull", true). All(&rows) if err != nil { return index } for _, row := range rows { index.rows[row.ID] = row index.names[row.ID] = row.OrgName index.children[row.ParentID] = append(index.children[row.ParentID], row.ID) } return index } // subtreeIDs 返回节点自身及其全部下级组织 ID。 func (index *scOrgIndex) subtreeIDs(root uint64) map[uint64]bool { result := map[uint64]bool{} if root == 0 { return result } queue := []uint64{root} for len(queue) > 0 { current := queue[0] queue = queue[1:] if result[current] { continue } result[current] = true queue = append(queue, index.children[current]...) } return result } func (index *scOrgIndex) name(id uint64) string { if name, ok := index.names[id]; ok { return name } return "" } // scBaseEmployeeQuery 排班可选员工:未删除、非离职。 func (c *BackendOaSchedulingController) scBaseEmployeeQuery(tid uint64) orm.QuerySeter { return models.Orm.QueryTable(new(models.BackendEmployee)). Filter("tid", tid). Filter("delete_time__isnull", true). Exclude("account_status", 2) } func scEmployeeDepartmentID(row models.BackendEmployee) uint64 { if row.Department == nil { return 0 } id, err := strconv.ParseUint(strings.TrimSpace(*row.Department), 10, 64) if err != nil { return 0 } return id } func (c *BackendOaSchedulingController) scEmployeeDTOs(tid uint64, rows []models.BackendEmployee) []schedulingEmployee { index := c.scLoadOrgIndex(tid) result := make([]schedulingEmployee, 0, len(rows)) for _, row := range rows { orgID := scEmployeeDepartmentID(row) position := "" if row.Position != nil { position = *row.Position } phone := "" if row.Phone != nil { phone = *row.Phone } result = append(result, schedulingEmployee{ ID: row.ID, Name: row.Name, Account: row.Account, Department: strconv.FormatUint(orgID, 10), DepartmentName: index.name(orgID), Position: position, Phone: phone, AccountStatus: row.AccountStatus, }) } return result } // scEmployeesByOrg 按组织节点取员工;includeChild=true 时包含全部下级组织, // 这样选中“公司/部门/小组”任意层级都能覆盖到该范围内的所有人。 func (c *BackendOaSchedulingController) scEmployeesByOrg(tid, orgID uint64, includeChild bool) ([]models.BackendEmployee, error) { qs := c.scBaseEmployeeQuery(tid) if orgID > 0 { allowed := map[uint64]bool{orgID: true} if includeChild { allowed = c.scLoadOrgIndex(tid).subtreeIDs(orgID) } values := make([]string, 0, len(allowed)) for id := range allowed { values = append(values, strconv.FormatUint(id, 10)) } qs = qs.Filter("department__in", values) } var rows []models.BackendEmployee _, err := qs.OrderBy("department", "id").All(&rows) if err != nil && err != orm.ErrNoRows { return nil, err } return rows, nil } // scEmployeesByIDs 按员工 ID 取员工,保持传入顺序由前端自行处理。 func (c *BackendOaSchedulingController) scEmployeesByIDs(tid uint64, ids []uint64) ([]models.BackendEmployee, error) { if len(ids) == 0 { return nil, nil } var rows []models.BackendEmployee _, err := c.scBaseEmployeeQuery(tid).Filter("id__in", ids).All(&rows) if err != nil && err != orm.ErrNoRows { return nil, err } return rows, nil } // scResolveEmployees 把方案范围(公司 / 部门小组 / 指定员工)展开成员工列表。 func (c *BackendOaSchedulingController) scResolveEmployees(tid uint64, scopeType int8, orgID uint64, includeChild bool, employeeIDs []uint64) ([]models.BackendEmployee, error) { switch scopeType { case models.OaScopeEmployees: return c.scEmployeesByIDs(tid, employeeIDs) case models.OaScopeCompany: return c.scEmployeesByOrg(tid, 0, false) default: return c.scEmployeesByOrg(tid, orgID, includeChild) } } // scWorkdays 从日期区间中筛出命中的星期(1=周一 ... 7=周日)。 // 未选择任何星期表示每天。 func scWorkdays(dates []string, weekdays map[int]bool) []string { if len(weekdays) == 0 { return dates } result := make([]string, 0, len(dates)) for _, date := range dates { parsed, ok := scParseDate(date) if !ok { continue } weekday := int(parsed.Weekday()) if weekday == 0 { weekday = 7 } if weekdays[weekday] { result = append(result, date) } } return result }