676 lines
19 KiB
Go
676 lines
19 KiB
Go
package controllers
|
||
|
||
import (
|
||
"encoding/json"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"server/models"
|
||
"server/pkg/identity"
|
||
"server/pkg/jwtutil"
|
||
|
||
"github.com/beego/beego/v2/client/orm"
|
||
beego "github.com/beego/beego/v2/server/web"
|
||
)
|
||
|
||
// BackendJobRankController 职级管理接口。
|
||
//
|
||
// 职级(Job Rank)定义「职位等级」:每个部门可以各设一套职级阶梯,
|
||
// department_id 指向 yz_backend_organization.id,department_id = 0 表示
|
||
// 全公司通用职级,任何部门都能选用。
|
||
//
|
||
// 职级的使用链路:
|
||
//
|
||
// 员工 → 职位(yz_backend_position.rank_id) → 职级
|
||
// 薪酬方案(yz_backend_oa_compensation_schemes.rank_id) → 职级
|
||
//
|
||
// 所有查询与写入都强制带上 JWT 中的租户ID(tid),实现租户间隔离;
|
||
// 前端传入的 tid / tenant_id 一律忽略,避免越权读取其它租户的数据。
|
||
type BackendJobRankController struct {
|
||
beego.Controller
|
||
}
|
||
|
||
// jobRankDTO 职级返回结构。
|
||
type jobRankDTO struct {
|
||
ID uint64 `json:"id"`
|
||
Tid uint64 `json:"tid"`
|
||
TenantID uint64 `json:"tenant_id"`
|
||
RankCode string `json:"rank_code"`
|
||
RankName string `json:"rank_name"`
|
||
LevelNo int `json:"level_no"`
|
||
DepartmentID uint64 `json:"department_id"`
|
||
DepartmentName string `json:"department_name"`
|
||
SalaryMin float64 `json:"salary_min"`
|
||
SalaryMax float64 `json:"salary_max"`
|
||
DefaultRoleID uint64 `json:"default_role_id"`
|
||
Status int8 `json:"status"`
|
||
Sort int `json:"sort"`
|
||
Remark string `json:"remark"`
|
||
CreateTime string `json:"create_time"`
|
||
}
|
||
|
||
// jobRankPayload 职级新增/编辑入参。全部使用指针,区分「没传」与「传了零值」。
|
||
type jobRankPayload struct {
|
||
RankCode *string `json:"rank_code"`
|
||
RankName *string `json:"rank_name"`
|
||
LevelNo *int `json:"level_no"`
|
||
DepartmentID *uint64 `json:"department_id"`
|
||
SalaryMin *float64 `json:"salary_min"`
|
||
SalaryMax *float64 `json:"salary_max"`
|
||
DefaultRoleID *uint64 `json:"default_role_id"`
|
||
Status *int8 `json:"status"`
|
||
Sort *int `json:"sort"`
|
||
Remark *string `json:"remark"`
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 认证与响应
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// claims 解析 Authorization 头中的 JWT,要求是租户端(backend)用户且带有效租户ID。
|
||
func (c *BackendJobRankController) claims() (*jwtutil.Claims, bool) {
|
||
claims, err := identity.Claims(c.Ctx.Request.Header.Get("Authorization"), identity.EndBackend)
|
||
if err != nil || claims.TenantId <= 0 {
|
||
return nil, false
|
||
}
|
||
return claims, true
|
||
}
|
||
|
||
// pathUint64 读取路径参数中的无符号整数。
|
||
func (c *BackendJobRankController) pathUint64(name string) (uint64, bool) {
|
||
id, err := strconv.ParseUint(c.Ctx.Input.Param(name), 10, 64)
|
||
return id, err == nil && id > 0
|
||
}
|
||
|
||
// nowString 当前时间字符串(软删除用)。
|
||
func (c *BackendJobRankController) nowString() string {
|
||
return time.Now().Format("2006-01-02 15:04:05")
|
||
}
|
||
|
||
// tenantID 取当前登录租户ID,未通过认证时直接输出 401 并返回 false。
|
||
func (c *BackendJobRankController) tenantID() (uint64, bool) {
|
||
claims, ok := c.claims()
|
||
if !ok {
|
||
c.jsonError(401, "未登录或登录已过期")
|
||
return 0, false
|
||
}
|
||
return uint64(claims.TenantId), true
|
||
}
|
||
|
||
func (c *BackendJobRankController) jsonOK(data interface{}) {
|
||
resp := map[string]interface{}{"code": 200, "msg": "success"}
|
||
if data != nil {
|
||
resp["data"] = data
|
||
}
|
||
c.Data["json"] = resp
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
func (c *BackendJobRankController) jsonError(code int, msg string) {
|
||
if code == 401 {
|
||
c.Ctx.Output.SetStatus(401)
|
||
}
|
||
c.Data["json"] = map[string]interface{}{"code": code, "msg": msg}
|
||
_ = c.ServeJSON()
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 查询基座
|
||
// ---------------------------------------------------------------------------
|
||
|
||
func (c *BackendJobRankController) rankQuery(tid uint64) orm.QuerySeter {
|
||
return models.Orm.QueryTable(new(models.BackendJobRank)).
|
||
Filter("tid", tid).
|
||
Filter("delete_time__isnull", true)
|
||
}
|
||
|
||
// jobRankPayloadOf 解析请求体(JSON 优先,兼容表单提交)。
|
||
func (c *BackendJobRankController) jobRankPayloadOf() (jobRankPayload, bool) {
|
||
var payload jobRankPayload
|
||
contentType := strings.ToLower(c.Ctx.Input.Header("Content-Type"))
|
||
if strings.Contains(contentType, "json") && len(c.Ctx.Input.RequestBody) > 0 {
|
||
if err := json.Unmarshal(c.Ctx.Input.RequestBody, &payload); err != nil {
|
||
return payload, false
|
||
}
|
||
return payload, true
|
||
}
|
||
// 表单提交:逐字段回退
|
||
getString := func(key string) *string {
|
||
v := strings.TrimSpace(c.GetString(key))
|
||
if v == "" {
|
||
return nil
|
||
}
|
||
return &v
|
||
}
|
||
getFloat := func(key string) *float64 {
|
||
v := strings.TrimSpace(c.GetString(key))
|
||
if v == "" {
|
||
return nil
|
||
}
|
||
f, err := strconv.ParseFloat(v, 64)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
return &f
|
||
}
|
||
getInt := func(key string) *int {
|
||
v := strings.TrimSpace(c.GetString(key))
|
||
if v == "" {
|
||
return nil
|
||
}
|
||
i, err := strconv.Atoi(v)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
return &i
|
||
}
|
||
getUint64 := func(key string) *uint64 {
|
||
v := strings.TrimSpace(c.GetString(key))
|
||
if v == "" {
|
||
return nil
|
||
}
|
||
i, err := strconv.ParseUint(v, 10, 64)
|
||
if err != nil {
|
||
return nil
|
||
}
|
||
return &i
|
||
}
|
||
payload.RankCode = getString("rank_code")
|
||
payload.RankName = getString("rank_name")
|
||
payload.LevelNo = getInt("level_no")
|
||
payload.DepartmentID = getUint64("department_id")
|
||
payload.SalaryMin = getFloat("salary_min")
|
||
payload.SalaryMax = getFloat("salary_max")
|
||
payload.DefaultRoleID = getUint64("default_role_id")
|
||
payload.Sort = getInt("sort")
|
||
payload.Remark = getString("remark")
|
||
if v := getString("status"); v != nil {
|
||
status := int8(0)
|
||
if s, err := strconv.Atoi(*v); err == nil {
|
||
status = int8(s)
|
||
}
|
||
payload.Status = &status
|
||
}
|
||
return payload, true
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 职级 CRUD
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// List 职级列表。
|
||
// GET /backend/jobrank/list
|
||
//
|
||
// 参数:
|
||
// keyword 职级名称 / 编码 模糊匹配
|
||
// department_id 适用部门筛选
|
||
// status 状态筛选(1 启用 / 0 禁用)
|
||
// scope=assign 返回「该部门可用」的职级:部门自身 + 全公司通用(department_id=0)。
|
||
// 职位、薪酬方案选择职级时用这个口径;不传则返回租户全部职级。
|
||
func (c *BackendJobRankController) List() {
|
||
tid, ok := c.tenantID()
|
||
if !ok {
|
||
return
|
||
}
|
||
|
||
cond := orm.NewCondition().And("tid", tid).And("delete_time__isnull", true)
|
||
departmentID, _ := c.GetUint64("department_id")
|
||
scope := strings.ToLower(strings.TrimSpace(c.GetString("scope")))
|
||
if scope == "assign" {
|
||
// 可选范围 = 本部门职级 + 全公司通用职级;
|
||
// 尚未指定部门时不按部门收窄,先把全部职级摆出来供选择。
|
||
if departmentID > 0 {
|
||
cond = cond.And("department_id__in", []uint64{0, departmentID})
|
||
}
|
||
} else if departmentID > 0 {
|
||
cond = cond.And("department_id", departmentID)
|
||
}
|
||
if keyword := strings.TrimSpace(c.GetString("keyword")); keyword != "" {
|
||
cond = cond.AndCond(orm.NewCondition().
|
||
Or("rank_name__icontains", keyword).
|
||
Or("rank_code__icontains", keyword))
|
||
}
|
||
if raw := strings.TrimSpace(c.GetString("status")); raw != "" {
|
||
if status, err := strconv.Atoi(raw); err == nil {
|
||
cond = cond.And("status", int8(status))
|
||
}
|
||
}
|
||
|
||
var rows []models.BackendJobRank
|
||
if _, err := models.Orm.QueryTable(new(models.BackendJobRank)).
|
||
SetCond(cond).OrderBy("level_no", "sort", "id").All(&rows); err != nil {
|
||
c.jsonError(500, "查询职级失败: "+err.Error())
|
||
return
|
||
}
|
||
|
||
deptNames := jobRankDeptNameMap(tid)
|
||
list := make([]jobRankDTO, 0, len(rows))
|
||
for _, row := range rows {
|
||
list = append(list, jobRankDTOOf(row, deptNames))
|
||
}
|
||
c.jsonOK(list)
|
||
}
|
||
|
||
// Detail 职级详情。
|
||
// GET /backend/jobrank/detail/:id
|
||
func (c *BackendJobRankController) Detail() {
|
||
tid, ok := c.tenantID()
|
||
if !ok {
|
||
return
|
||
}
|
||
id, valid := c.pathUint64(":id")
|
||
if !valid {
|
||
c.jsonError(400, "无效ID")
|
||
return
|
||
}
|
||
var row models.BackendJobRank
|
||
if err := c.rankQuery(tid).Filter("id", id).One(&row); err != nil {
|
||
c.jsonError(404, "职级不存在")
|
||
return
|
||
}
|
||
c.jsonOK(jobRankDTOOf(row, jobRankDeptNameMap(tid)))
|
||
}
|
||
|
||
// Create 新增职级。
|
||
// POST /backend/jobrank/create
|
||
func (c *BackendJobRankController) Create() {
|
||
tid, ok := c.tenantID()
|
||
if !ok {
|
||
return
|
||
}
|
||
payload, ok := c.jobRankPayloadOf()
|
||
if !ok {
|
||
c.jsonError(400, "参数错误")
|
||
return
|
||
}
|
||
|
||
rankName := ""
|
||
if payload.RankName != nil {
|
||
rankName = strings.TrimSpace(*payload.RankName)
|
||
}
|
||
if rankName == "" {
|
||
c.jsonError(400, "职级名称不能为空")
|
||
return
|
||
}
|
||
|
||
rankCode := ""
|
||
if payload.RankCode != nil {
|
||
rankCode = strings.TrimSpace(*payload.RankCode)
|
||
}
|
||
if rankCode == "" {
|
||
c.jsonError(400, "职级编码不能为空")
|
||
return
|
||
}
|
||
count, err := c.rankQuery(tid).Filter("rank_code", rankCode).Count()
|
||
if err != nil {
|
||
c.jsonError(500, "校验职级编码失败: "+err.Error())
|
||
return
|
||
}
|
||
if count > 0 {
|
||
c.jsonError(400, "职级编码已存在")
|
||
return
|
||
}
|
||
|
||
departmentID := uint64(0)
|
||
if payload.DepartmentID != nil {
|
||
departmentID = *payload.DepartmentID
|
||
}
|
||
if departmentID > 0 && !jobRankOrgExists(tid, departmentID) {
|
||
c.jsonError(400, "适用部门不存在")
|
||
return
|
||
}
|
||
|
||
salaryMin, salaryMax := 0.0, 0.0
|
||
if payload.SalaryMin != nil {
|
||
salaryMin = *payload.SalaryMin
|
||
}
|
||
if payload.SalaryMax != nil {
|
||
salaryMax = *payload.SalaryMax
|
||
}
|
||
if salaryMin < 0 || salaryMax < 0 {
|
||
c.jsonError(400, "标准薪资不能为负数")
|
||
return
|
||
}
|
||
if salaryMax > 0 && salaryMax < salaryMin {
|
||
c.jsonError(400, "标准薪资上限不能小于下限")
|
||
return
|
||
}
|
||
|
||
row := models.BackendJobRank{
|
||
Tid: tid,
|
||
RankCode: rankCode,
|
||
RankName: rankName,
|
||
LevelNo: 0,
|
||
DepartmentID: departmentID,
|
||
SalaryMin: salaryMin,
|
||
SalaryMax: salaryMax,
|
||
DefaultRoleID: 0,
|
||
Status: 1,
|
||
Sort: 0,
|
||
}
|
||
if payload.LevelNo != nil {
|
||
row.LevelNo = *payload.LevelNo
|
||
}
|
||
if payload.DefaultRoleID != nil {
|
||
row.DefaultRoleID = *payload.DefaultRoleID
|
||
}
|
||
if payload.Status != nil {
|
||
row.Status = int8(1)
|
||
if *payload.Status == 0 {
|
||
row.Status = 0
|
||
}
|
||
}
|
||
if payload.Sort != nil {
|
||
row.Sort = *payload.Sort
|
||
}
|
||
if payload.Remark != nil {
|
||
row.Remark = strPtrIfNotEmpty(strings.TrimSpace(*payload.Remark))
|
||
}
|
||
|
||
id, err := models.Orm.Insert(&row)
|
||
if err != nil {
|
||
c.jsonError(500, "创建职级失败: "+err.Error())
|
||
return
|
||
}
|
||
c.jsonOK(map[string]interface{}{"id": id, "rank_code": rankCode})
|
||
}
|
||
|
||
// Update 更新职级。
|
||
// POST /backend/jobrank/update/:id
|
||
func (c *BackendJobRankController) Update() {
|
||
tid, ok := c.tenantID()
|
||
if !ok {
|
||
return
|
||
}
|
||
id, valid := c.pathUint64(":id")
|
||
if !valid {
|
||
c.jsonError(400, "无效ID")
|
||
return
|
||
}
|
||
if !c.rankQuery(tid).Filter("id", id).Exist() {
|
||
c.jsonError(404, "职级不存在")
|
||
return
|
||
}
|
||
|
||
payload, ok := c.jobRankPayloadOf()
|
||
if !ok {
|
||
c.jsonError(400, "参数错误")
|
||
return
|
||
}
|
||
|
||
update := orm.Params{}
|
||
|
||
if payload.RankCode != nil {
|
||
code := strings.TrimSpace(*payload.RankCode)
|
||
if code == "" {
|
||
c.jsonError(400, "职级编码不能为空")
|
||
return
|
||
}
|
||
count, err := c.rankQuery(tid).Filter("rank_code", code).Exclude("id", id).Count()
|
||
if err != nil {
|
||
c.jsonError(500, "校验职级编码失败: "+err.Error())
|
||
return
|
||
}
|
||
if count > 0 {
|
||
c.jsonError(400, "职级编码已存在")
|
||
return
|
||
}
|
||
update["rank_code"] = code
|
||
}
|
||
if payload.RankName != nil {
|
||
name := strings.TrimSpace(*payload.RankName)
|
||
if name == "" {
|
||
c.jsonError(400, "职级名称不能为空")
|
||
return
|
||
}
|
||
update["rank_name"] = name
|
||
}
|
||
if payload.LevelNo != nil {
|
||
update["level_no"] = *payload.LevelNo
|
||
}
|
||
if payload.DepartmentID != nil {
|
||
departmentID := *payload.DepartmentID
|
||
if departmentID > 0 && !jobRankOrgExists(tid, departmentID) {
|
||
c.jsonError(400, "适用部门不存在")
|
||
return
|
||
}
|
||
update["department_id"] = departmentID
|
||
}
|
||
if payload.SalaryMin != nil || payload.SalaryMax != nil {
|
||
// 薪资区间必须整体校验:只传一端时用另一端的现值补齐。
|
||
var row models.BackendJobRank
|
||
if err := c.rankQuery(tid).Filter("id", id).One(&row); err != nil {
|
||
c.jsonError(404, "职级不存在")
|
||
return
|
||
}
|
||
salaryMin, salaryMax := row.SalaryMin, row.SalaryMax
|
||
if payload.SalaryMin != nil {
|
||
salaryMin = *payload.SalaryMin
|
||
}
|
||
if payload.SalaryMax != nil {
|
||
salaryMax = *payload.SalaryMax
|
||
}
|
||
if salaryMin < 0 || salaryMax < 0 {
|
||
c.jsonError(400, "标准薪资不能为负数")
|
||
return
|
||
}
|
||
if salaryMax > 0 && salaryMax < salaryMin {
|
||
c.jsonError(400, "标准薪资上限不能小于下限")
|
||
return
|
||
}
|
||
update["salary_min"] = salaryMin
|
||
update["salary_max"] = salaryMax
|
||
}
|
||
if payload.DefaultRoleID != nil {
|
||
// 预留字段:本期不启用职级→角色权限绑定,仅落库保存。
|
||
update["default_role_id"] = *payload.DefaultRoleID
|
||
}
|
||
if payload.Status != nil {
|
||
status := int8(1)
|
||
if *payload.Status == 0 {
|
||
status = 0
|
||
}
|
||
update["status"] = status
|
||
}
|
||
if payload.Sort != nil {
|
||
update["sort"] = *payload.Sort
|
||
}
|
||
if payload.Remark != nil {
|
||
update["remark"] = nullableString(strings.TrimSpace(*payload.Remark))
|
||
}
|
||
|
||
if len(update) == 0 {
|
||
c.jsonError(400, "无更新字段")
|
||
return
|
||
}
|
||
if _, err := c.rankQuery(tid).Filter("id", id).Update(update); err != nil {
|
||
c.jsonError(500, "更新职级失败: "+err.Error())
|
||
return
|
||
}
|
||
c.jsonOK(nil)
|
||
}
|
||
|
||
// Delete 删除职级(软删除)。
|
||
// DELETE /backend/jobrank/delete/:id
|
||
func (c *BackendJobRankController) Delete() {
|
||
tid, ok := c.tenantID()
|
||
if !ok {
|
||
return
|
||
}
|
||
id, valid := c.pathUint64(":id")
|
||
if !valid {
|
||
c.jsonError(400, "无效ID")
|
||
return
|
||
}
|
||
|
||
// 还有职位引用该职级时不允许删除:否则职级消失后职位会指向空职级,
|
||
// 薪酬/权限按职级取数时拿不到任何标准值。
|
||
positionCount, err := models.Orm.QueryTable(new(models.BackendPosition)).
|
||
Filter("tid", tid).
|
||
Filter("rank_id", id).
|
||
Filter("delete_time__isnull", true).
|
||
Count()
|
||
if err != nil {
|
||
c.jsonError(500, "校验职级引用失败: "+err.Error())
|
||
return
|
||
}
|
||
if positionCount > 0 {
|
||
c.jsonError(400, "该职级已被 "+strconv.FormatInt(positionCount, 10)+" 个职位引用,请先调整职位的职级")
|
||
return
|
||
}
|
||
// 薪酬方案同样引用职级:删掉职级会让方案拿不到标准薪资区间,
|
||
// 历史方案也会指向空职级,因此一并拦下。
|
||
schemeCount, err := models.Orm.QueryTable(new(models.BackendOaCompensationScheme)).
|
||
Filter("tid", tid).
|
||
Filter("rank_id", id).
|
||
Filter("is_deleted", 0).
|
||
Count()
|
||
if err != nil {
|
||
c.jsonError(500, "校验职级引用失败: "+err.Error())
|
||
return
|
||
}
|
||
if schemeCount > 0 {
|
||
c.jsonError(400, "该职级已被 "+strconv.FormatInt(schemeCount, 10)+" 个薪酬方案引用,请先调整方案的职级")
|
||
return
|
||
}
|
||
|
||
num, err := c.rankQuery(tid).Filter("id", id).
|
||
Update(orm.Params{"delete_time": c.nowString(), "status": int8(0)})
|
||
if err != nil {
|
||
c.jsonError(500, "删除职级失败: "+err.Error())
|
||
return
|
||
}
|
||
if num == 0 {
|
||
c.jsonError(404, "职级不存在")
|
||
return
|
||
}
|
||
c.jsonOK(nil)
|
||
}
|
||
|
||
// CheckCodeUnique 检查职级编码唯一性。
|
||
// GET /backend/jobrank/checkCodeUnique?rank_code=xx&exclude_id=1
|
||
func (c *BackendJobRankController) CheckCodeUnique() {
|
||
tid, ok := c.tenantID()
|
||
if !ok {
|
||
return
|
||
}
|
||
code := strings.TrimSpace(c.GetString("rank_code"))
|
||
if code == "" {
|
||
c.jsonError(400, "职级编码不能为空")
|
||
return
|
||
}
|
||
excludeID, _ := c.GetUint64("exclude_id")
|
||
qs := c.rankQuery(tid).Filter("rank_code", code)
|
||
if excludeID > 0 {
|
||
qs = qs.Exclude("id", excludeID)
|
||
}
|
||
count, err := qs.Count()
|
||
if err != nil {
|
||
c.jsonError(500, "检查编码唯一性失败: "+err.Error())
|
||
return
|
||
}
|
||
c.jsonOK(map[string]interface{}{"unique": count == 0, "message": uniqueMessage(count == 0, "编码")})
|
||
}
|
||
|
||
// ---------------------------------------------------------------------------
|
||
// 供其它模块复用的辅助函数
|
||
// ---------------------------------------------------------------------------
|
||
|
||
// jobRankDTOOf 把模型行转成 DTO,并补上部门名称。
|
||
func jobRankDTOOf(row models.BackendJobRank, deptNames map[uint64]string) jobRankDTO {
|
||
deptName := ""
|
||
if row.DepartmentID > 0 {
|
||
deptName = deptNames[row.DepartmentID]
|
||
}
|
||
if row.DepartmentID == 0 {
|
||
deptName = "全公司通用"
|
||
}
|
||
return jobRankDTO{
|
||
ID: row.ID,
|
||
Tid: row.Tid,
|
||
TenantID: row.Tid,
|
||
RankCode: row.RankCode,
|
||
RankName: row.RankName,
|
||
LevelNo: row.LevelNo,
|
||
DepartmentID: row.DepartmentID,
|
||
DepartmentName: deptName,
|
||
SalaryMin: row.SalaryMin,
|
||
SalaryMax: row.SalaryMax,
|
||
DefaultRoleID: row.DefaultRoleID,
|
||
Status: row.Status,
|
||
Sort: row.Sort,
|
||
Remark: derefString(row.Remark),
|
||
CreateTime: formatDateTime(&row.CreateTime),
|
||
}
|
||
}
|
||
|
||
// jobRankDeptNameMap 组织ID -> 名称,用于展示职级适用部门。
|
||
func jobRankDeptNameMap(tid uint64) map[uint64]string {
|
||
result := map[uint64]string{}
|
||
var rows []models.BackendOrganization
|
||
if _, err := models.Orm.QueryTable(new(models.BackendOrganization)).
|
||
Filter("tid", tid).
|
||
Filter("delete_time__isnull", true).
|
||
All(&rows, "ID", "OrgName"); err != nil {
|
||
return result
|
||
}
|
||
for _, row := range rows {
|
||
result[row.ID] = row.OrgName
|
||
}
|
||
return result
|
||
}
|
||
|
||
// jobRankOrgExists 判断部门是否存在(同租户)。
|
||
func jobRankOrgExists(tid, id uint64) bool {
|
||
if id == 0 {
|
||
return false
|
||
}
|
||
return models.Orm.QueryTable(new(models.BackendOrganization)).
|
||
Filter("tid", tid).
|
||
Filter("id", id).
|
||
Filter("delete_time__isnull", true).
|
||
Exist()
|
||
}
|
||
|
||
// JobRankMap 返回租户内的职级映射(ID -> 模型行),供职位、薪酬等模块复用。
|
||
func JobRankMap(tid uint64) map[uint64]models.BackendJobRank {
|
||
result := map[uint64]models.BackendJobRank{}
|
||
if tid == 0 {
|
||
return result
|
||
}
|
||
var rows []models.BackendJobRank
|
||
if _, err := models.Orm.QueryTable(new(models.BackendJobRank)).
|
||
Filter("tid", tid).
|
||
Filter("delete_time__isnull", true).
|
||
All(&rows); err != nil {
|
||
return result
|
||
}
|
||
for _, row := range rows {
|
||
result[row.ID] = row
|
||
}
|
||
return result
|
||
}
|
||
|
||
// ResolveEmployeeRankID 解析员工对应的职级:
|
||
// 员工上记录的职位是「职位名称」,先按 部门+职位名 找到职位,再取职位上的职级。
|
||
// 找不到职位或职位未设置职级时返回 0。
|
||
func ResolveEmployeeRankID(tid uint64, employee models.BackendEmployee) uint64 {
|
||
positionName := strings.TrimSpace(derefString(employee.Position))
|
||
if positionName == "" {
|
||
return 0
|
||
}
|
||
qs := models.Orm.QueryTable(new(models.BackendPosition)).
|
||
Filter("tid", tid).
|
||
Filter("position_name", positionName).
|
||
Filter("delete_time__isnull", true)
|
||
if departmentID, err := strconv.ParseUint(strings.TrimSpace(derefString(employee.Department)), 10, 64); err == nil && departmentID > 0 {
|
||
qs = qs.Filter("department_id", departmentID)
|
||
}
|
||
var row models.BackendPosition
|
||
if err := qs.OrderBy("id").One(&row); err != nil {
|
||
return 0
|
||
}
|
||
return row.RankID
|
||
}
|