package controllers import ( "crypto/sha256" "encoding/hex" "encoding/json" "errors" "fmt" "strconv" "strings" "time" "server/models" ) // 本文件包含组织架构模块的 DTO 组装、树结构与层级校验,以及请求参数解析工具。 // --------------------------------------------------------------------------- // DTO 组装 // --------------------------------------------------------------------------- // organizationDTOList 批量组装组织 DTO。一次性把父组织名、负责人名、员工数查出来, // 避免逐行查询导致的 N+1 问题。 func (c *BackendOrganizationController) organizationDTOList(tid uint64, rows []models.BackendOrganization) []organizationDTO { list := make([]organizationDTO, 0, len(rows)) if len(rows) == 0 { return list } nameByID := c.orgNameMap(tid) leaderNames := c.employeeNameMap(tid) for _, row := range rows { list = append(list, c.assembleOrganizationDTO(row, nameByID, leaderNames)) } return list } func (c *BackendOrganizationController) organizationDTO(tid uint64, row models.BackendOrganization) organizationDTO { return c.assembleOrganizationDTO(row, c.orgNameMap(tid), c.employeeNameMap(tid)) } func (c *BackendOrganizationController) assembleOrganizationDTO( row models.BackendOrganization, nameByID map[uint64]string, leaderNames map[uint64]string, ) organizationDTO { leaderID := uint64(0) if row.LeaderID != nil { leaderID = *row.LeaderID } return organizationDTO{ ID: row.ID, Tid: row.Tid, TenantID: row.Tid, OrgName: row.OrgName, OrgCode: row.OrgCode, ParentID: row.ParentID, ParentName: nameByID[row.ParentID], LeaderID: leaderID, LeaderName: leaderNames[leaderID], IsCompany: row.IsCompany, Sort: row.Sort, Status: row.Status, Remark: derefString(row.Remark), CreateTime: formatDateTime(&row.CreateTime), UpdateTime: formatDateTime(&row.UpdateTime), } } func (c *BackendOrganizationController) employeeDTOList(tid uint64, rows []models.BackendEmployee) []employeeDTO { list := make([]employeeDTO, 0, len(rows)) if len(rows) == 0 { return list } nameByID := c.orgNameMap(tid) for _, row := range rows { list = append(list, c.assembleEmployeeDTO(row, nameByID)) } return list } func (c *BackendOrganizationController) employeeDTO(tid uint64, row models.BackendEmployee) employeeDTO { return c.assembleEmployeeDTO(row, c.orgNameMap(tid)) } func (c *BackendOrganizationController) assembleEmployeeDTO( row models.BackendEmployee, nameByID map[uint64]string, ) employeeDTO { tid := 0 if row.Tid != nil { tid = *row.Tid } birthday := "" if row.Birthday != nil { birthday = row.Birthday.Format("2006-01-02") } affiliateUnit := derefString(row.AffiliateUnit) department := derefString(row.Department) return employeeDTO{ ID: row.ID, Tid: tid, TenantID: tid, Account: row.Account, Name: row.Name, Gender: row.Gender, Sex: row.Gender, Birthday: birthday, AffiliateUnit: affiliateUnit, AffiliateUnitName: orgNameByIDString(nameByID, affiliateUnit), Department: department, DepartmentName: orgNameByIDString(nameByID, department), Position: derefString(row.Position), Education: derefString(row.Education), Nation: derefString(row.Nation), Phone: derefString(row.Phone), Wechat: derefString(row.Wechat), Email: derefString(row.Email), HomeAddress: derefString(row.HomeAddress), AccountStatus: row.AccountStatus, Status: row.AccountStatus, CreateTime: formatDateTime(&row.CreateTime), } } func (c *BackendOrganizationController) positionDTO(tid uint64, row models.BackendPosition) positionDTO { nameByID := c.orgNameMap(tid) return positionDTO{ ID: row.ID, Tid: row.Tid, TenantID: row.Tid, DepartmentID: row.DepartmentID, DepartmentName: nameByID[row.DepartmentID], PositionCode: row.PositionCode, PositionName: row.PositionName, PositionType: row.PositionType, Status: row.Status, Sort: row.Sort, Remark: derefString(row.Remark), CreateTime: formatDateTime(&row.CreateTime), } } // --------------------------------------------------------------------------- // 组织关系查询 // --------------------------------------------------------------------------- func (c *BackendOrganizationController) orgNameMap(tid uint64) map[uint64]string { result := map[uint64]string{} var rows []models.BackendOrganization if _, err := c.orgQuery(tid).All(&rows, "ID", "OrgName"); err != nil { return result } for _, row := range rows { result[row.ID] = row.OrgName } return result } func (c *BackendOrganizationController) employeeNameMap(tid uint64) map[uint64]string { result := map[uint64]string{} var rows []models.BackendEmployee if _, err := c.employeeQuery(tid).All(&rows, "ID", "Name"); err != nil { return result } for _, row := range rows { result[uint64(row.ID)] = row.Name } return result } func (c *BackendOrganizationController) orgExists(tid, id uint64) bool { if id == 0 { return false } return c.orgQuery(tid).Filter("id", id).Exist() } // parentMap 返回 组织ID -> 上级组织ID 的映射,用于层级与环路判断。 func (c *BackendOrganizationController) parentMap(tid uint64) map[uint64]uint64 { result := map[uint64]uint64{} var rows []models.BackendOrganization if _, err := c.orgQuery(tid).All(&rows, "ID", "ParentID"); err != nil { return result } for _, row := range rows { result[row.ID] = row.ParentID } return result } // orgDepth 计算组织所在层级,顶级为 1。 func (c *BackendOrganizationController) orgDepth(tid, id uint64) (int, error) { parents := c.parentMap(tid) depth := 0 current := id for current > 0 { depth++ if depth > 64 { return depth, errors.New("组织层级数据异常(可能存在环路)") } next, ok := parents[current] if !ok { break } current = next } return depth, nil } // subtreeHeight 计算以 id 为根的子树高度(只有自身时为 1)。 func (c *BackendOrganizationController) subtreeHeight(tid, id uint64) int { childrenOf := map[uint64][]uint64{} var rows []models.BackendOrganization if _, err := c.orgQuery(tid).All(&rows, "ID", "ParentID"); err != nil { return 1 } for _, row := range rows { childrenOf[row.ParentID] = append(childrenOf[row.ParentID], row.ID) } return subtreeHeightFrom(childrenOf, id, 0) } func subtreeHeightFrom(childrenOf map[uint64][]uint64, id uint64, depth int) int { if depth > 64 { return depth } height := 1 for _, child := range childrenOf[id] { if h := subtreeHeightFrom(childrenOf, child, depth+1) + 1; h > height { height = h } } return height } // collectOrgIDs 返回 rootID 及其所有下级组织的ID。 func (c *BackendOrganizationController) collectOrgIDs(tid, rootID uint64) []uint64 { childrenOf := map[uint64][]uint64{} var rows []models.BackendOrganization if _, err := c.orgQuery(tid).All(&rows, "ID", "ParentID"); err != nil { return []uint64{rootID} } for _, row := range rows { childrenOf[row.ParentID] = append(childrenOf[row.ParentID], row.ID) } result := []uint64{rootID} queue := []uint64{rootID} visited := map[uint64]bool{rootID: true} for len(queue) > 0 { current := queue[0] queue = queue[1:] for _, child := range childrenOf[current] { if visited[child] { continue } visited[child] = true result = append(result, child) queue = append(queue, child) } } return result } // rootCompanyID 沿上级链向上找到所属的顶层公司ID。 func (c *BackendOrganizationController) rootCompanyID(tid uint64, org models.BackendOrganization) uint64 { if org.IsCompany == 1 { return org.ID } parents := c.parentMap(tid) current := org.ParentID for i := 0; i < 64 && current > 0; i++ { var row models.BackendOrganization if err := c.orgQuery(tid).Filter("id", current).One(&row); err != nil { return 0 } if row.IsCompany == 1 { return row.ID } next, ok := parents[current] if !ok { return 0 } current = next } return 0 } // validateParentChange 校验把 orgID 挂到 newParentID 下是否合法: // 不能挂到自己或自己的后代(形成环路),且移动后总层级不超过设置上限。 func (c *BackendOrganizationController) validateParentChange(tid, orgID, newParentID uint64, settings orgSettings) error { if newParentID == 0 { return nil } if newParentID == orgID { return errors.New("上级组织不能是自己") } if !c.orgExists(tid, newParentID) { return errors.New("上级组织不存在") } for _, id := range c.collectOrgIDs(tid, orgID) { if id == newParentID { return errors.New("不能将组织移动到自己的下级组织中") } } parentDepth, err := c.orgDepth(tid, newParentID) if err != nil { return err } if settings.MaxOrgLevels > 0 && parentDepth+c.subtreeHeight(tid, orgID) > settings.MaxOrgLevels { return fmt.Errorf("移动后组织层级将超过上限 %d 级", settings.MaxOrgLevels) } if settings.MaxOrgChildren > 0 { count, err := c.orgQuery(tid).Filter("parent_id", newParentID). Exclude("id", orgID).Exclude("status", 0).Count() if err == nil && int(count) >= settings.MaxOrgChildren { return fmt.Errorf("同一上级下最多 %d 个子组织", settings.MaxOrgChildren) } } return nil } // validateEmployeeOrg 校验员工的隶属单位与部门必须是当前租户下已存在的组织。 func (c *BackendOrganizationController) validateEmployeeOrg(tid uint64, affiliateUnit, department string) error { for label, raw := range map[string]string{"隶属单位": affiliateUnit, "部门": department} { raw = strings.TrimSpace(raw) if raw == "" { continue } id, err := strconv.ParseUint(raw, 10, 64) if err != nil { return fmt.Errorf("%s格式不正确", label) } if !c.orgExists(tid, id) { return fmt.Errorf("%s不存在", label) } } return nil } // buildOrganizationTree 把扁平的组织 DTO 列表组装成树。 // 上级不在列表中的节点(如上级被禁用)作为根节点返回,避免数据丢失。 func buildOrganizationTree(list []organizationDTO) []map[string]interface{} { nodeMap := make(map[uint64]map[string]interface{}, len(list)) order := make([]uint64, 0, len(list)) for _, item := range list { raw, _ := json.Marshal(item) node := map[string]interface{}{} _ = json.Unmarshal(raw, &node) node["children"] = make([]map[string]interface{}, 0) nodeMap[item.ID] = node order = append(order, item.ID) } tree := make([]map[string]interface{}, 0) for _, id := range order { node := nodeMap[id] parentID := uint64(0) if v, ok := node["parent_id"].(float64); ok { parentID = uint64(v) } if parent, exists := nodeMap[parentID]; parentID > 0 && exists { parent["children"] = append(parent["children"].([]map[string]interface{}), node) continue } tree = append(tree, node) } return tree } // treeDepth 计算树的最大深度。 func treeDepth(childrenOf map[uint64][]uint64, rootID uint64, depth int) int { if depth > 64 { return depth } maxDepth := depth for _, child := range childrenOf[rootID] { if d := treeDepth(childrenOf, child, depth+1); d > maxDepth { maxDepth = d } } return maxDepth } func orgNameByIDString(nameByID map[uint64]string, raw string) string { raw = strings.TrimSpace(raw) if raw == "" { return "" } id, err := strconv.ParseUint(raw, 10, 64) if err != nil { return "" } return nameByID[id] } func uniqueMessage(unique bool, subject string) string { if unique { return subject + "可用" } return subject + "已存在" } func formatDateTime(t *time.Time) string { if t == nil || t.IsZero() { return "" } return t.Format("2006-01-02 15:04:05") } func clampInt(v, min, max int) int { if v < min { return min } if v > max { return max } return v } func maxInt(a, b int) int { if a > b { return a } return b } // generateCode 生成形如 ORG20260827193012 的编码,长度不足时补时间戳末尾数字。 func (c *BackendOrganizationController) generateCode(prefix string, length int) string { prefix = strings.TrimSpace(prefix) stamp := time.Now().Format("20060102150405") code := prefix + stamp if length > 0 && len(code) > length && len(prefix) < length { keep := length - len(prefix) code = prefix + stamp[len(stamp)-keep:] } return code } // hashEmployeePassword 适配 password varchar(64),使用 sha256 hex;空密码返回空串。 func hashEmployeePassword(plain string) string { plain = strings.TrimSpace(plain) if plain == "" { return "" } sum := sha256.Sum256([]byte(plain)) return hex.EncodeToString(sum[:]) }