1451 lines
39 KiB
Go
1451 lines
39 KiB
Go
package controllers
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"server/models"
|
|
"server/pkg/jwtutil"
|
|
|
|
"github.com/beego/beego/v2/client/orm"
|
|
beego "github.com/beego/beego/v2/server/web"
|
|
)
|
|
|
|
// BackendOrganizationController 组织架构(组织、员工、职位)接口。
|
|
//
|
|
// 组织架构是租户端的通用基础数据:进销存(ERP)与办公自动化(OA)两个模块各自有独立界面,
|
|
// 但读写的是同一份数据。所有查询与写入都强制带上 JWT 中的租户ID(tid),实现租户间隔离,
|
|
// 前端传入的 tid / tenant_id 一律忽略,避免越权读取其它租户的数据。
|
|
type BackendOrganizationController struct {
|
|
beego.Controller
|
|
}
|
|
|
|
const orgSettingsCodePrefix = "backend_org_settings"
|
|
|
|
type orgSettings struct {
|
|
OrgCodePrefix string `json:"org_code_prefix"`
|
|
EmployeeCodePrefix string `json:"employee_code_prefix"`
|
|
PositionCodePrefix string `json:"position_code_prefix"`
|
|
AutoGenerateCodes bool `json:"auto_generate_codes"`
|
|
CodeLength int `json:"code_length"`
|
|
DefaultOrgType int `json:"default_org_type"`
|
|
DefaultSort int `json:"default_sort"`
|
|
DefaultStatus int `json:"default_status"`
|
|
MaxOrgLevels int `json:"max_org_levels"`
|
|
MaxOrgChildren int `json:"max_org_children"`
|
|
AllowDuplicateCode bool `json:"allow_duplicate_codes"`
|
|
BatchOperations bool `json:"batch_operations"`
|
|
ExportEnabled bool `json:"export_enabled"`
|
|
ImportEnabled bool `json:"import_enabled"`
|
|
}
|
|
|
|
func defaultOrgSettings() orgSettings {
|
|
return orgSettings{
|
|
OrgCodePrefix: "ORG",
|
|
EmployeeCodePrefix: "EMP",
|
|
PositionCodePrefix: "POS",
|
|
AutoGenerateCodes: true,
|
|
CodeLength: 8,
|
|
DefaultOrgType: 0,
|
|
DefaultSort: 0,
|
|
DefaultStatus: 1,
|
|
MaxOrgLevels: 10,
|
|
MaxOrgChildren: 50,
|
|
AllowDuplicateCode: false,
|
|
BatchOperations: true,
|
|
ExportEnabled: true,
|
|
ImportEnabled: true,
|
|
}
|
|
}
|
|
|
|
type organizationDTO struct {
|
|
ID uint64 `json:"id"`
|
|
Tid uint64 `json:"tid"`
|
|
TenantID uint64 `json:"tenant_id"`
|
|
OrgName string `json:"org_name"`
|
|
OrgCode string `json:"org_code"`
|
|
ParentID uint64 `json:"parent_id"`
|
|
ParentName string `json:"parent_name"`
|
|
LeaderID uint64 `json:"leader_id"`
|
|
LeaderName string `json:"leader_name"`
|
|
IsCompany int `json:"is_company"`
|
|
Sort uint `json:"sort"`
|
|
Status int8 `json:"status"`
|
|
Remark string `json:"remark"`
|
|
EmployeeCount int64 `json:"employee_count"`
|
|
CreateTime string `json:"create_time"`
|
|
UpdateTime string `json:"update_time"`
|
|
}
|
|
|
|
type employeeDTO struct {
|
|
ID uint `json:"id"`
|
|
Tid int `json:"tid"`
|
|
TenantID int `json:"tenant_id"`
|
|
Account string `json:"account"`
|
|
Name string `json:"name"`
|
|
Gender int8 `json:"gender"`
|
|
Sex int8 `json:"sex"`
|
|
Birthday string `json:"birthday"`
|
|
AffiliateUnit string `json:"affiliate_unit"`
|
|
AffiliateUnitName string `json:"affiliate_unit_name"`
|
|
Department string `json:"department"`
|
|
DepartmentName string `json:"department_name"`
|
|
Position string `json:"position"`
|
|
Education string `json:"education"`
|
|
Nation string `json:"nation"`
|
|
Phone string `json:"phone"`
|
|
Wechat string `json:"wechat"`
|
|
Email string `json:"email"`
|
|
HomeAddress string `json:"home_address"`
|
|
AccountStatus int8 `json:"account_status"`
|
|
Status int8 `json:"status"`
|
|
CreateTime string `json:"create_time"`
|
|
}
|
|
|
|
type positionDTO struct {
|
|
ID uint64 `json:"id"`
|
|
Tid uint64 `json:"tid"`
|
|
TenantID uint64 `json:"tenant_id"`
|
|
DepartmentID uint64 `json:"department_id"`
|
|
DepartmentName string `json:"department_name"`
|
|
PositionCode string `json:"position_code"`
|
|
PositionName string `json:"position_name"`
|
|
PositionType int8 `json:"position_type"`
|
|
Status int8 `json:"status"`
|
|
Sort uint `json:"sort"`
|
|
Remark string `json:"remark"`
|
|
CreateTime string `json:"create_time"`
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 认证与响应
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// claims 解析 Authorization 头中的 JWT,要求是租户端(backend)用户且带有效租户ID。
|
|
func (c *BackendOrganizationController) claims() (*jwtutil.Claims, bool) {
|
|
auth := strings.TrimSpace(c.Ctx.Request.Header.Get("Authorization"))
|
|
parts := strings.SplitN(auth, " ", 2)
|
|
if len(parts) != 2 || parts[0] != "Bearer" {
|
|
return nil, false
|
|
}
|
|
claims, err := jwtutil.ParseToken(parts[1])
|
|
if err != nil || claims.UserType != "backend" || claims.TenantId <= 0 {
|
|
return nil, false
|
|
}
|
|
return claims, true
|
|
}
|
|
|
|
// tenantID 取当前登录租户ID,未通过认证时直接输出 401 并返回 false。
|
|
func (c *BackendOrganizationController) tenantID() (uint64, bool) {
|
|
claims, ok := c.claims()
|
|
if !ok {
|
|
c.jsonError(401, "未登录或登录已过期")
|
|
return 0, false
|
|
}
|
|
return uint64(claims.TenantId), true
|
|
}
|
|
|
|
func (c *BackendOrganizationController) 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 *BackendOrganizationController) 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()
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 查询基座:所有 QuerySeter 都强制附加 tid 过滤
|
|
// ---------------------------------------------------------------------------
|
|
|
|
func (c *BackendOrganizationController) orgQuery(tid uint64) orm.QuerySeter {
|
|
return models.Orm.QueryTable(new(models.BackendOrganization)).
|
|
Filter("tid", tid).
|
|
Filter("delete_time__isnull", true)
|
|
}
|
|
|
|
func (c *BackendOrganizationController) employeeQuery(tid uint64) orm.QuerySeter {
|
|
return models.Orm.QueryTable(new(models.BackendEmployee)).
|
|
Filter("tid", tid).
|
|
Filter("delete_time__isnull", true)
|
|
}
|
|
|
|
func (c *BackendOrganizationController) positionQuery(tid uint64) orm.QuerySeter {
|
|
return models.Orm.QueryTable(new(models.BackendPosition)).
|
|
Filter("tid", tid).
|
|
Filter("delete_time__isnull", true)
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 组织机构
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// GetOrganization 获取组织机构列表(扁平)。
|
|
// GET /backend/{erp|oa}/getOrganization
|
|
func (c *BackendOrganizationController) GetOrganization() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
qs := c.orgQuery(tid).Exclude("status", 0)
|
|
if keyword := strings.TrimSpace(c.GetString("keyword")); keyword != "" {
|
|
qs = qs.Filter("org_name__icontains", keyword)
|
|
}
|
|
|
|
var rows []models.BackendOrganization
|
|
if _, err := qs.OrderBy("sort", "id").All(&rows); err != nil {
|
|
c.jsonError(500, "查询组织机构失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
c.jsonOK(c.organizationDTOList(tid, rows))
|
|
}
|
|
|
|
// GetOrganizationDetail 获取组织机构详情。
|
|
// GET /backend/{erp|oa}/getOrganizationDetail/:id
|
|
func (c *BackendOrganizationController) GetOrganizationDetail() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
id, valid := c.pathUint64(":id")
|
|
if !valid {
|
|
c.jsonError(400, "无效ID")
|
|
return
|
|
}
|
|
|
|
var row models.BackendOrganization
|
|
if err := c.orgQuery(tid).Filter("id", id).Exclude("status", 0).One(&row); err != nil {
|
|
c.jsonError(404, "组织机构不存在")
|
|
return
|
|
}
|
|
|
|
c.jsonOK(c.organizationDTO(tid, row))
|
|
}
|
|
|
|
// CreateOrganization 创建组织机构。
|
|
// POST /backend/{erp|oa}/createOrganization
|
|
func (c *BackendOrganizationController) CreateOrganization() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
body := c.parseJSONBody()
|
|
settings := c.loadOrgSettings(tid)
|
|
|
|
orgName, _ := c.getStringValue(body, "org_name", "name")
|
|
orgName = strings.TrimSpace(orgName)
|
|
if orgName == "" {
|
|
c.jsonError(400, "组织名称不能为空")
|
|
return
|
|
}
|
|
|
|
parentID, _ := c.getUint64Value(body, "parent_id")
|
|
if parentID > 0 {
|
|
if !c.orgExists(tid, parentID) {
|
|
c.jsonError(400, "上级组织不存在")
|
|
return
|
|
}
|
|
depth, err := c.orgDepth(tid, parentID)
|
|
if err != nil {
|
|
c.jsonError(500, "校验组织层级失败: "+err.Error())
|
|
return
|
|
}
|
|
if settings.MaxOrgLevels > 0 && depth+1 > settings.MaxOrgLevels {
|
|
c.jsonError(400, fmt.Sprintf("组织层级最多 %d 级", settings.MaxOrgLevels))
|
|
return
|
|
}
|
|
if settings.MaxOrgChildren > 0 {
|
|
count, err := c.orgQuery(tid).Filter("parent_id", parentID).Exclude("status", 0).Count()
|
|
if err == nil && int(count) >= settings.MaxOrgChildren {
|
|
c.jsonError(400, fmt.Sprintf("同一上级下最多 %d 个子组织", settings.MaxOrgChildren))
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
orgCode, _ := c.getStringValue(body, "org_code", "code")
|
|
orgCode = strings.TrimSpace(orgCode)
|
|
if orgCode == "" {
|
|
if !settings.AutoGenerateCodes {
|
|
c.jsonError(400, "组织编码不能为空")
|
|
return
|
|
}
|
|
orgCode = c.generateCode(settings.OrgCodePrefix, settings.CodeLength)
|
|
}
|
|
if !settings.AllowDuplicateCode {
|
|
count, err := c.orgQuery(tid).Filter("org_code", orgCode).Count()
|
|
if err != nil {
|
|
c.jsonError(500, "校验组织编码失败: "+err.Error())
|
|
return
|
|
}
|
|
if count > 0 {
|
|
c.jsonError(400, "组织编码已存在")
|
|
return
|
|
}
|
|
}
|
|
|
|
leaderID, hasLeader := c.getUint64Value(body, "leader_id")
|
|
sortVal, hasSort := c.getUintValue(body, "sort")
|
|
isCompany, hasCompany := c.getIntValue(body, "is_company")
|
|
status, hasStatus := c.getIntValue(body, "status")
|
|
remark, _ := c.getStringValue(body, "remark")
|
|
|
|
row := models.BackendOrganization{
|
|
Tid: tid,
|
|
OrgName: orgName,
|
|
OrgCode: orgCode,
|
|
ParentID: parentID,
|
|
Sort: uint(settings.DefaultSort),
|
|
IsCompany: boolInt(parentID == 0),
|
|
Status: int8(settings.DefaultStatus),
|
|
Remark: strPtrIfNotEmpty(remark),
|
|
}
|
|
if hasSort {
|
|
row.Sort = sortVal
|
|
}
|
|
if hasLeader && leaderID > 0 {
|
|
row.LeaderID = &leaderID
|
|
}
|
|
if hasCompany {
|
|
row.IsCompany = isCompany
|
|
}
|
|
if hasStatus {
|
|
row.Status = int8(status)
|
|
}
|
|
|
|
id, err := models.Orm.Insert(&row)
|
|
if err != nil {
|
|
c.jsonError(500, "创建组织机构失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
c.jsonOK(map[string]interface{}{"id": id, "org_code": orgCode})
|
|
}
|
|
|
|
// EditOrganization 更新组织机构。
|
|
// POST /backend/{erp|oa}/editOrganization/:id
|
|
func (c *BackendOrganizationController) EditOrganization() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
id, valid := c.pathUint64(":id")
|
|
if !valid {
|
|
c.jsonError(400, "无效ID")
|
|
return
|
|
}
|
|
if !c.orgExists(tid, id) {
|
|
c.jsonError(404, "组织机构不存在")
|
|
return
|
|
}
|
|
|
|
body := c.parseJSONBody()
|
|
settings := c.loadOrgSettings(tid)
|
|
update := orm.Params{}
|
|
|
|
if v, has := c.getStringValue(body, "org_name", "name"); has {
|
|
v = strings.TrimSpace(v)
|
|
if v == "" {
|
|
c.jsonError(400, "组织名称不能为空")
|
|
return
|
|
}
|
|
update["org_name"] = v
|
|
}
|
|
if v, has := c.getStringValue(body, "org_code", "code"); has {
|
|
v = strings.TrimSpace(v)
|
|
if v == "" {
|
|
c.jsonError(400, "组织编码不能为空")
|
|
return
|
|
}
|
|
if !settings.AllowDuplicateCode {
|
|
count, err := c.orgQuery(tid).Filter("org_code", v).Exclude("id", id).Count()
|
|
if err != nil {
|
|
c.jsonError(500, "校验组织编码失败: "+err.Error())
|
|
return
|
|
}
|
|
if count > 0 {
|
|
c.jsonError(400, "组织编码已存在")
|
|
return
|
|
}
|
|
}
|
|
update["org_code"] = v
|
|
}
|
|
if v, has := c.getUint64Value(body, "parent_id"); has {
|
|
if err := c.validateParentChange(tid, id, v, settings); err != nil {
|
|
c.jsonError(400, err.Error())
|
|
return
|
|
}
|
|
update["parent_id"] = v
|
|
}
|
|
if v, has := c.getUint64Value(body, "leader_id"); has {
|
|
update["leader_id"] = nullableUint64(v)
|
|
}
|
|
if v, has := c.getUintValue(body, "sort"); has {
|
|
update["sort"] = v
|
|
}
|
|
if v, has := c.getIntValue(body, "is_company"); has {
|
|
update["is_company"] = v
|
|
}
|
|
if v, has := c.getIntValue(body, "status"); has {
|
|
update["status"] = int8(v)
|
|
}
|
|
if v, has := c.getStringValue(body, "remark"); has {
|
|
update["remark"] = nullableString(v)
|
|
}
|
|
|
|
if len(update) == 0 {
|
|
c.jsonError(400, "无更新字段")
|
|
return
|
|
}
|
|
|
|
if _, err := c.orgQuery(tid).Filter("id", id).Update(update); err != nil {
|
|
c.jsonError(500, "更新组织机构失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
c.jsonOK(nil)
|
|
}
|
|
|
|
// DeleteOrganization 删除组织机构(软删除)。
|
|
// DELETE /backend/{erp|oa}/deleteOrganization/:id
|
|
func (c *BackendOrganizationController) DeleteOrganization() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
id, valid := c.pathUint64(":id")
|
|
if !valid {
|
|
c.jsonError(400, "无效ID")
|
|
return
|
|
}
|
|
|
|
childCount, err := c.orgQuery(tid).Filter("parent_id", id).Exclude("status", 0).Count()
|
|
if err != nil {
|
|
c.jsonError(500, "检查子组织失败: "+err.Error())
|
|
return
|
|
}
|
|
if childCount > 0 {
|
|
c.jsonError(400, "请先删除下级组织")
|
|
return
|
|
}
|
|
|
|
employeeCount, err := c.employeeQuery(tid).Filter("department", strconv.FormatUint(id, 10)).Count()
|
|
if err != nil {
|
|
c.jsonError(500, "检查组织员工失败: "+err.Error())
|
|
return
|
|
}
|
|
if employeeCount > 0 {
|
|
c.jsonError(400, "该组织下还有员工,请先调岗或删除员工")
|
|
return
|
|
}
|
|
|
|
num, err := c.orgQuery(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)
|
|
}
|
|
|
|
// GetCompanys 获取企业单位列表。
|
|
// GET /backend/{erp|oa}/getCompanys
|
|
func (c *BackendOrganizationController) GetCompanys() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var rows []models.BackendOrganization
|
|
_, err := c.orgQuery(tid).Exclude("status", 0).Filter("is_company", 1).
|
|
OrderBy("sort", "id").All(&rows)
|
|
if err != nil {
|
|
c.jsonError(500, "查询企业单位失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
c.jsonOK(c.organizationDTOList(tid, rows))
|
|
}
|
|
|
|
// GetDepartments 获取部门列表。
|
|
// GET /backend/{erp|oa}/getDepartments?parent_id=1
|
|
func (c *BackendOrganizationController) GetDepartments() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
parentID, _ := c.GetUint64("parent_id")
|
|
|
|
qs := c.orgQuery(tid).Exclude("status", 0)
|
|
if parentID > 0 {
|
|
qs = qs.Filter("parent_id", parentID)
|
|
} else {
|
|
qs = qs.Filter("is_company", 0)
|
|
}
|
|
|
|
var rows []models.BackendOrganization
|
|
if _, err := qs.OrderBy("sort", "id").All(&rows); err != nil {
|
|
c.jsonError(500, "查询部门失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
c.jsonOK(c.organizationDTOList(tid, rows))
|
|
}
|
|
|
|
// GetOrganizationTree 获取组织架构树。
|
|
// GET /backend/{erp|oa}/getOrganizationTree
|
|
func (c *BackendOrganizationController) GetOrganizationTree() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var rows []models.BackendOrganization
|
|
_, err := c.orgQuery(tid).Exclude("status", 0).OrderBy("sort", "id").All(&rows)
|
|
if err != nil {
|
|
c.jsonError(500, "查询组织机构失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
c.jsonOK(buildOrganizationTree(c.organizationDTOList(tid, rows)))
|
|
}
|
|
|
|
// SearchOrganizations 按名称/编码搜索组织机构。
|
|
// GET /backend/{erp|oa}/searchOrganizations?keyword=xx
|
|
func (c *BackendOrganizationController) SearchOrganizations() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
keyword := strings.TrimSpace(c.GetString("keyword"))
|
|
|
|
cond := orm.NewCondition().
|
|
And("tid", tid).
|
|
And("delete_time__isnull", true).
|
|
AndNot("status", 0)
|
|
if keyword != "" {
|
|
cond = cond.AndCond(orm.NewCondition().
|
|
Or("org_name__icontains", keyword).
|
|
Or("org_code__icontains", keyword))
|
|
}
|
|
|
|
var rows []models.BackendOrganization
|
|
_, err := models.Orm.QueryTable(new(models.BackendOrganization)).
|
|
SetCond(cond).OrderBy("sort", "id").All(&rows)
|
|
if err != nil {
|
|
c.jsonError(500, "查询组织机构失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
c.jsonOK(c.organizationDTOList(tid, rows))
|
|
}
|
|
|
|
// GetOrganizationHierarchy 获取指定组织的上级链与直接下级。
|
|
// GET /backend/{erp|oa}/getOrganizationHierarchy/:org_id
|
|
func (c *BackendOrganizationController) GetOrganizationHierarchy() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
orgID, valid := c.pathUint64(":org_id")
|
|
if !valid {
|
|
c.jsonError(400, "无效组织ID")
|
|
return
|
|
}
|
|
|
|
var org models.BackendOrganization
|
|
if err := c.orgQuery(tid).Filter("id", orgID).One(&org); err != nil {
|
|
c.jsonError(404, "组织不存在")
|
|
return
|
|
}
|
|
|
|
var children []models.BackendOrganization
|
|
if _, err := c.orgQuery(tid).Filter("parent_id", orgID).Exclude("status", 0).
|
|
OrderBy("sort", "id").All(&children); err != nil {
|
|
c.jsonError(500, "查询子组织失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// 自底向上收集上级链,最多回溯 64 层,防止脏数据造成死循环
|
|
ancestors := make([]organizationDTO, 0)
|
|
parentID := org.ParentID
|
|
for i := 0; i < 64 && parentID > 0; i++ {
|
|
var parent models.BackendOrganization
|
|
if err := c.orgQuery(tid).Filter("id", parentID).One(&parent); err != nil {
|
|
break
|
|
}
|
|
ancestors = append([]organizationDTO{c.organizationDTO(tid, parent)}, ancestors...)
|
|
parentID = parent.ParentID
|
|
}
|
|
|
|
c.jsonOK(map[string]interface{}{
|
|
"current": c.organizationDTO(tid, org),
|
|
"ancestors": ancestors,
|
|
"children": c.organizationDTOList(tid, children),
|
|
})
|
|
}
|
|
|
|
// GetOrganizationStats 组织架构统计。
|
|
// GET /backend/{erp|oa}/getOrganizationStats
|
|
func (c *BackendOrganizationController) GetOrganizationStats() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
var rows []models.BackendOrganization
|
|
if _, err := c.orgQuery(tid).All(&rows); err != nil {
|
|
c.jsonError(500, "统计组织架构失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
childrenOf := map[uint64][]uint64{}
|
|
for _, row := range rows {
|
|
childrenOf[row.ParentID] = append(childrenOf[row.ParentID], row.ID)
|
|
}
|
|
|
|
companyCount, departmentCount, enabledCount := 0, 0, 0
|
|
for _, row := range rows {
|
|
if row.IsCompany == 1 {
|
|
companyCount++
|
|
} else {
|
|
departmentCount++
|
|
}
|
|
if row.Status == 1 {
|
|
enabledCount++
|
|
}
|
|
}
|
|
|
|
employeeTotal, _ := c.employeeQuery(tid).Count()
|
|
employeeActive, _ := c.employeeQuery(tid).Filter("account_status", 1).Count()
|
|
positionTotal, _ := c.positionQuery(tid).Count()
|
|
|
|
c.jsonOK(map[string]interface{}{
|
|
"org_total": len(rows),
|
|
"company_count": companyCount,
|
|
"department_count": departmentCount,
|
|
"enabled_count": enabledCount,
|
|
"disabled_count": len(rows) - enabledCount,
|
|
"max_depth": treeDepth(childrenOf, 0, 0),
|
|
"employee_total": employeeTotal,
|
|
"employee_active": employeeActive,
|
|
"position_total": positionTotal,
|
|
})
|
|
}
|
|
|
|
// MoveOrganization 移动组织节点到新的上级(拖拽调整层级)。
|
|
// POST /backend/{erp|oa}/moveOrganization
|
|
func (c *BackendOrganizationController) MoveOrganization() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
body := c.parseJSONBody()
|
|
|
|
orgID, hasOrg := c.getUint64Value(body, "org_id", "id")
|
|
if !hasOrg || orgID == 0 {
|
|
c.jsonError(400, "组织ID不能为空")
|
|
return
|
|
}
|
|
parentID, _ := c.getUint64Value(body, "parent_id")
|
|
|
|
if !c.orgExists(tid, orgID) {
|
|
c.jsonError(404, "组织不存在")
|
|
return
|
|
}
|
|
settings := c.loadOrgSettings(tid)
|
|
if err := c.validateParentChange(tid, orgID, parentID, settings); err != nil {
|
|
c.jsonError(400, err.Error())
|
|
return
|
|
}
|
|
|
|
update := orm.Params{"parent_id": parentID, "is_company": boolInt(parentID == 0)}
|
|
if v, has := c.getUintValue(body, "sort"); has {
|
|
update["sort"] = v
|
|
}
|
|
if _, err := c.orgQuery(tid).Filter("id", orgID).Update(update); err != nil {
|
|
c.jsonError(500, "移动组织失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
c.jsonOK(nil)
|
|
}
|
|
|
|
// BatchOrganizeOrganizations 批量启用/禁用/删除组织。
|
|
// POST /backend/{erp|oa}/batchOrganizeOrganizations
|
|
func (c *BackendOrganizationController) BatchOrganizeOrganizations() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
body := c.parseJSONBody()
|
|
|
|
ids := c.getUint64Slice(body, "ids")
|
|
if len(ids) == 0 {
|
|
c.jsonError(400, "请选择要操作的组织")
|
|
return
|
|
}
|
|
action, _ := c.getStringValue(body, "action")
|
|
action = strings.ToLower(strings.TrimSpace(action))
|
|
|
|
var update orm.Params
|
|
switch action {
|
|
case "enable":
|
|
update = orm.Params{"status": int8(1)}
|
|
case "disable":
|
|
update = orm.Params{"status": int8(0)}
|
|
case "delete":
|
|
// 有下级或有员工的组织不允许批量删除,避免产生孤儿数据
|
|
for _, id := range ids {
|
|
childCount, _ := c.orgQuery(tid).Filter("parent_id", id).Exclude("status", 0).Count()
|
|
if childCount > 0 {
|
|
c.jsonError(400, "选中组织存在下级组织,无法批量删除")
|
|
return
|
|
}
|
|
employeeCount, _ := c.employeeQuery(tid).Filter("department", strconv.FormatUint(id, 10)).Count()
|
|
if employeeCount > 0 {
|
|
c.jsonError(400, "选中组织下仍有员工,无法批量删除")
|
|
return
|
|
}
|
|
}
|
|
update = orm.Params{"delete_time": c.nowString(), "status": int8(0)}
|
|
default:
|
|
c.jsonError(400, "不支持的操作类型")
|
|
return
|
|
}
|
|
|
|
num, err := c.orgQuery(tid).Filter("id__in", ids).Update(update)
|
|
if err != nil {
|
|
c.jsonError(500, "批量操作失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
c.jsonOK(map[string]interface{}{"affected": num})
|
|
}
|
|
|
|
// CheckOrgCodeUnique 检查组织编码唯一性。
|
|
// GET /backend/{erp|oa}/checkOrgCodeUnique?org_code=xx&exclude_id=1
|
|
func (c *BackendOrganizationController) CheckOrgCodeUnique() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
orgCode := strings.TrimSpace(c.GetString("org_code"))
|
|
if orgCode == "" {
|
|
c.jsonError(400, "组织编码不能为空")
|
|
return
|
|
}
|
|
excludeID, _ := c.GetUint64("exclude_id")
|
|
if excludeID == 0 {
|
|
excludeID, _ = c.GetUint64("org_id")
|
|
}
|
|
|
|
qs := c.orgQuery(tid).Filter("org_code", orgCode)
|
|
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, "编码")})
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 员工
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// GetEmployee 获取员工列表,支持按组织、关键词、状态过滤。
|
|
// GET /backend/{erp|oa}/getEmployee?org_id=1&keyword=xx&status=1
|
|
func (c *BackendOrganizationController) GetEmployee() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
cond := orm.NewCondition().And("tid", tid).And("delete_time__isnull", true)
|
|
|
|
orgID, _ := c.GetUint64("org_id")
|
|
if orgID == 0 {
|
|
orgID, _ = c.GetUint64("department_id")
|
|
}
|
|
if orgID > 0 {
|
|
// 含下级组织:把整棵子树的组织ID都算进去
|
|
orgIDs := c.collectOrgIDs(tid, orgID)
|
|
values := make([]string, 0, len(orgIDs))
|
|
for _, id := range orgIDs {
|
|
values = append(values, strconv.FormatUint(id, 10))
|
|
}
|
|
cond = cond.And("department__in", values)
|
|
}
|
|
if keyword := strings.TrimSpace(c.GetString("keyword")); keyword != "" {
|
|
cond = cond.AndCond(orm.NewCondition().
|
|
Or("name__icontains", keyword).
|
|
Or("account__icontains", keyword).
|
|
Or("phone__icontains", keyword))
|
|
}
|
|
if raw := strings.TrimSpace(c.GetString("status")); raw != "" {
|
|
if status, err := strconv.Atoi(raw); err == nil {
|
|
cond = cond.And("account_status", int8(status))
|
|
}
|
|
}
|
|
|
|
var rows []models.BackendEmployee
|
|
_, err := models.Orm.QueryTable(new(models.BackendEmployee)).
|
|
SetCond(cond).OrderBy("-id").All(&rows)
|
|
if err != nil {
|
|
c.jsonError(500, "查询员工失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
c.jsonOK(c.employeeDTOList(tid, rows))
|
|
}
|
|
|
|
// GetOrganizationEmployees 获取指定组织(含下级)的员工列表。
|
|
// GET /backend/{erp|oa}/getOrganizationEmployees/:org_id
|
|
func (c *BackendOrganizationController) GetOrganizationEmployees() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
orgID, valid := c.pathUint64(":org_id")
|
|
if !valid {
|
|
c.jsonError(400, "无效组织ID")
|
|
return
|
|
}
|
|
|
|
orgIDs := c.collectOrgIDs(tid, orgID)
|
|
values := make([]string, 0, len(orgIDs))
|
|
for _, id := range orgIDs {
|
|
values = append(values, strconv.FormatUint(id, 10))
|
|
}
|
|
|
|
var rows []models.BackendEmployee
|
|
_, err := c.employeeQuery(tid).Filter("department__in", values).OrderBy("-id").All(&rows)
|
|
if err != nil {
|
|
c.jsonError(500, "查询员工失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
c.jsonOK(c.employeeDTOList(tid, rows))
|
|
}
|
|
|
|
// GetEmployeeDetail 获取员工详情。
|
|
// GET /backend/{erp|oa}/getEmployeeDetail/:id
|
|
func (c *BackendOrganizationController) GetEmployeeDetail() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
id, valid := c.pathUint(":id")
|
|
if !valid {
|
|
c.jsonError(400, "无效ID")
|
|
return
|
|
}
|
|
|
|
var row models.BackendEmployee
|
|
if err := c.employeeQuery(tid).Filter("id", id).One(&row); err != nil {
|
|
c.jsonError(404, "员工不存在")
|
|
return
|
|
}
|
|
|
|
c.jsonOK(c.employeeDTO(tid, row))
|
|
}
|
|
|
|
// CreateEmployee 创建员工。
|
|
// POST /backend/{erp|oa}/createEmployee
|
|
func (c *BackendOrganizationController) CreateEmployee() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
body := c.parseJSONBody()
|
|
settings := c.loadOrgSettings(tid)
|
|
|
|
name, _ := c.getStringValue(body, "name")
|
|
name = strings.TrimSpace(name)
|
|
if name == "" {
|
|
c.jsonError(400, "姓名不能为空")
|
|
return
|
|
}
|
|
|
|
account, _ := c.getStringValue(body, "account")
|
|
account = strings.TrimSpace(account)
|
|
if account == "" {
|
|
if !settings.AutoGenerateCodes {
|
|
c.jsonError(400, "账号不能为空")
|
|
return
|
|
}
|
|
account = c.generateCode(settings.EmployeeCodePrefix, settings.CodeLength)
|
|
}
|
|
count, err := c.employeeQuery(tid).Filter("account", account).Count()
|
|
if err != nil {
|
|
c.jsonError(500, "校验账号失败: "+err.Error())
|
|
return
|
|
}
|
|
if count > 0 {
|
|
c.jsonError(400, "账号已存在")
|
|
return
|
|
}
|
|
|
|
gender, hasGender := c.getIntValue(body, "gender", "sex")
|
|
status, hasStatus := c.getIntValue(body, "account_status", "status")
|
|
password, _ := c.getStringValue(body, "password")
|
|
birthday, _ := c.getStringValue(body, "birthday")
|
|
affiliateUnit, _ := c.getStringValue(body, "affiliate_unit")
|
|
department, _ := c.getStringValue(body, "department")
|
|
position, _ := c.getStringValue(body, "position")
|
|
education, _ := c.getStringValue(body, "education")
|
|
nation, _ := c.getStringValue(body, "nation")
|
|
phone, _ := c.getStringValue(body, "phone")
|
|
wechat, _ := c.getStringValue(body, "wechat")
|
|
email, _ := c.getStringValue(body, "email")
|
|
homeAddress, _ := c.getStringValue(body, "home_address")
|
|
|
|
if err := c.validateEmployeeOrg(tid, affiliateUnit, department); err != nil {
|
|
c.jsonError(400, err.Error())
|
|
return
|
|
}
|
|
|
|
tidInt := int(tid)
|
|
row := models.BackendEmployee{
|
|
Tid: &tidInt,
|
|
Account: account,
|
|
Password: hashEmployeePassword(password),
|
|
Name: name,
|
|
Gender: 0,
|
|
Birthday: parseDatePtr(birthday),
|
|
AffiliateUnit: strPtrIfNotEmpty(affiliateUnit),
|
|
Department: strPtrIfNotEmpty(department),
|
|
Position: strPtrIfNotEmpty(position),
|
|
Education: strPtrIfNotEmpty(education),
|
|
Nation: strPtrIfNotEmpty(nation),
|
|
Phone: strPtrIfNotEmpty(phone),
|
|
Wechat: strPtrIfNotEmpty(wechat),
|
|
Email: strPtrIfNotEmpty(email),
|
|
HomeAddress: strPtrIfNotEmpty(homeAddress),
|
|
AccountStatus: 1,
|
|
}
|
|
if hasGender {
|
|
row.Gender = int8(gender)
|
|
}
|
|
if hasStatus {
|
|
row.AccountStatus = int8(status)
|
|
}
|
|
|
|
id, err := models.Orm.Insert(&row)
|
|
if err != nil {
|
|
c.jsonError(500, "创建员工失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
c.jsonOK(map[string]interface{}{"id": id, "account": account})
|
|
}
|
|
|
|
// EditEmployee 更新员工。
|
|
// POST /backend/{erp|oa}/editEmployee/:id
|
|
func (c *BackendOrganizationController) EditEmployee() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
id, valid := c.pathUint(":id")
|
|
if !valid {
|
|
c.jsonError(400, "无效ID")
|
|
return
|
|
}
|
|
|
|
if !c.employeeQuery(tid).Filter("id", id).Exist() {
|
|
c.jsonError(404, "员工不存在")
|
|
return
|
|
}
|
|
|
|
body := c.parseJSONBody()
|
|
update := orm.Params{}
|
|
|
|
if v, has := c.getStringValue(body, "account"); has && strings.TrimSpace(v) != "" {
|
|
v = strings.TrimSpace(v)
|
|
count, err := c.employeeQuery(tid).Filter("account", v).Exclude("id", id).Count()
|
|
if err != nil {
|
|
c.jsonError(500, "校验账号失败: "+err.Error())
|
|
return
|
|
}
|
|
if count > 0 {
|
|
c.jsonError(400, "账号已存在")
|
|
return
|
|
}
|
|
update["account"] = v
|
|
}
|
|
if v, has := c.getStringValue(body, "name"); has {
|
|
v = strings.TrimSpace(v)
|
|
if v == "" {
|
|
c.jsonError(400, "姓名不能为空")
|
|
return
|
|
}
|
|
update["name"] = v
|
|
}
|
|
if v, has := c.getIntValue(body, "gender", "sex"); has {
|
|
update["gender"] = int8(v)
|
|
}
|
|
if v, has := c.getStringValue(body, "birthday"); has {
|
|
update["birthday"] = parseDatePtr(v)
|
|
}
|
|
|
|
affiliateUnit, hasAffiliate := c.getStringValue(body, "affiliate_unit")
|
|
department, hasDepartment := c.getStringValue(body, "department")
|
|
if hasAffiliate || hasDepartment {
|
|
if err := c.validateEmployeeOrg(tid, affiliateUnit, department); err != nil {
|
|
c.jsonError(400, err.Error())
|
|
return
|
|
}
|
|
}
|
|
if hasAffiliate {
|
|
update["affiliate_unit"] = nullableString(affiliateUnit)
|
|
}
|
|
if hasDepartment {
|
|
update["department"] = nullableString(department)
|
|
}
|
|
|
|
if v, has := c.getStringValue(body, "position"); has {
|
|
update["position"] = nullableString(v)
|
|
}
|
|
if v, has := c.getStringValue(body, "education"); has {
|
|
update["education"] = nullableString(v)
|
|
}
|
|
if v, has := c.getStringValue(body, "nation"); has {
|
|
update["nation"] = nullableString(v)
|
|
}
|
|
if v, has := c.getStringValue(body, "phone"); has {
|
|
update["phone"] = nullableString(v)
|
|
}
|
|
if v, has := c.getStringValue(body, "wechat"); has {
|
|
update["wechat"] = nullableString(v)
|
|
}
|
|
if v, has := c.getStringValue(body, "email"); has {
|
|
update["email"] = nullableString(v)
|
|
}
|
|
if v, has := c.getStringValue(body, "home_address"); has {
|
|
update["home_address"] = nullableString(v)
|
|
}
|
|
if v, has := c.getIntValue(body, "account_status", "status"); has {
|
|
update["account_status"] = int8(v)
|
|
}
|
|
if v, has := c.getStringValue(body, "password"); has && strings.TrimSpace(v) != "" {
|
|
update["password"] = hashEmployeePassword(v)
|
|
}
|
|
|
|
if len(update) == 0 {
|
|
c.jsonError(400, "无更新字段")
|
|
return
|
|
}
|
|
|
|
if _, err := c.employeeQuery(tid).Filter("id", id).Update(update); err != nil {
|
|
c.jsonError(500, "更新员工失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
c.jsonOK(nil)
|
|
}
|
|
|
|
// DeleteEmployee 删除员工(软删除)。
|
|
// DELETE /backend/{erp|oa}/deleteEmployee/:id
|
|
func (c *BackendOrganizationController) DeleteEmployee() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
id, valid := c.pathUint(":id")
|
|
if !valid {
|
|
c.jsonError(400, "无效ID")
|
|
return
|
|
}
|
|
|
|
num, err := c.employeeQuery(tid).Filter("id", id).
|
|
Update(orm.Params{"delete_time": c.nowString(), "account_status": int8(2)})
|
|
if err != nil {
|
|
c.jsonError(500, "删除员工失败: "+err.Error())
|
|
return
|
|
}
|
|
if num == 0 {
|
|
c.jsonError(404, "员工不存在")
|
|
return
|
|
}
|
|
|
|
c.jsonOK(nil)
|
|
}
|
|
|
|
// MoveEmployeeToOrg 员工调岗:批量或单个移动到指定组织。
|
|
// POST /backend/{erp|oa}/moveEmployeeToOrg
|
|
func (c *BackendOrganizationController) MoveEmployeeToOrg() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
body := c.parseJSONBody()
|
|
|
|
orgID, hasOrg := c.getUint64Value(body, "org_id", "department_id")
|
|
if !hasOrg || orgID == 0 {
|
|
c.jsonError(400, "组织ID不能为空")
|
|
return
|
|
}
|
|
|
|
ids := c.getUintSlice(body, "employee_ids", "ids")
|
|
if single, has := c.getUintValue(body, "employee_id"); has && single > 0 {
|
|
ids = append(ids, single)
|
|
}
|
|
if len(ids) == 0 {
|
|
c.jsonError(400, "请选择要调岗的员工")
|
|
return
|
|
}
|
|
|
|
var org models.BackendOrganization
|
|
if err := c.orgQuery(tid).Filter("id", orgID).One(&org); err != nil {
|
|
c.jsonError(400, "目标组织不存在")
|
|
return
|
|
}
|
|
|
|
update := orm.Params{"department": strconv.FormatUint(orgID, 10)}
|
|
// 部门变更时同步隶属单位:取该组织所在的顶层公司
|
|
if companyID := c.rootCompanyID(tid, org); companyID > 0 {
|
|
update["affiliate_unit"] = strconv.FormatUint(companyID, 10)
|
|
}
|
|
if v, has := c.getStringValue(body, "position"); has {
|
|
update["position"] = nullableString(v)
|
|
}
|
|
|
|
num, err := c.employeeQuery(tid).Filter("id__in", ids).Update(update)
|
|
if err != nil {
|
|
c.jsonError(500, "调岗失败: "+err.Error())
|
|
return
|
|
}
|
|
if num == 0 {
|
|
c.jsonError(404, "员工不存在")
|
|
return
|
|
}
|
|
|
|
c.jsonOK(map[string]interface{}{"affected": num})
|
|
}
|
|
|
|
// CheckEmployeeAccountUnique 检查员工账号唯一性。
|
|
// GET /backend/{erp|oa}/checkEmployeeAccountUnique?account=xx&exclude_id=1
|
|
func (c *BackendOrganizationController) CheckEmployeeAccountUnique() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
account := strings.TrimSpace(c.GetString("account"))
|
|
if account == "" {
|
|
c.jsonError(400, "员工账号不能为空")
|
|
return
|
|
}
|
|
excludeID, _ := c.GetUint64("exclude_id")
|
|
if excludeID == 0 {
|
|
excludeID, _ = c.GetUint64("employee_id")
|
|
}
|
|
|
|
qs := c.employeeQuery(tid).Filter("account", account)
|
|
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, "账号")})
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// 职位
|
|
// ---------------------------------------------------------------------------
|
|
|
|
// GetPosition 获取职位列表。
|
|
// GET /backend/{erp|oa}/getPosition?department_id=1&keyword=xx
|
|
func (c *BackendOrganizationController) GetPosition() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
|
|
cond := orm.NewCondition().And("tid", tid).And("delete_time__isnull", true)
|
|
if departmentID, _ := c.GetUint64("department_id"); departmentID > 0 {
|
|
cond = cond.And("department_id", departmentID)
|
|
}
|
|
if keyword := strings.TrimSpace(c.GetString("keyword")); keyword != "" {
|
|
cond = cond.AndCond(orm.NewCondition().
|
|
Or("position_name__icontains", keyword).
|
|
Or("position_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.BackendPosition
|
|
_, err := models.Orm.QueryTable(new(models.BackendPosition)).
|
|
SetCond(cond).OrderBy("sort", "id").All(&rows)
|
|
if err != nil {
|
|
c.jsonError(500, "查询职位失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
list := make([]positionDTO, 0, len(rows))
|
|
for _, row := range rows {
|
|
list = append(list, c.positionDTO(tid, row))
|
|
}
|
|
|
|
c.jsonOK(list)
|
|
}
|
|
|
|
// GetPositionDetail 获取职位详情。
|
|
// GET /backend/{erp|oa}/getPositionDetail/:id
|
|
func (c *BackendOrganizationController) GetPositionDetail() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
id, valid := c.pathUint64(":id")
|
|
if !valid {
|
|
c.jsonError(400, "无效ID")
|
|
return
|
|
}
|
|
|
|
var row models.BackendPosition
|
|
if err := c.positionQuery(tid).Filter("id", id).One(&row); err != nil {
|
|
c.jsonError(404, "职位不存在")
|
|
return
|
|
}
|
|
|
|
c.jsonOK(c.positionDTO(tid, row))
|
|
}
|
|
|
|
// CreatePosition 创建职位。
|
|
// POST /backend/{erp|oa}/createPosition
|
|
func (c *BackendOrganizationController) CreatePosition() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
body := c.parseJSONBody()
|
|
settings := c.loadOrgSettings(tid)
|
|
|
|
positionName, _ := c.getStringValue(body, "position_name", "name")
|
|
positionName = strings.TrimSpace(positionName)
|
|
if positionName == "" {
|
|
c.jsonError(400, "职位名称不能为空")
|
|
return
|
|
}
|
|
|
|
departmentID, _ := c.getUint64Value(body, "department_id")
|
|
if departmentID > 0 && !c.orgExists(tid, departmentID) {
|
|
c.jsonError(400, "所属部门不存在")
|
|
return
|
|
}
|
|
|
|
positionCode, _ := c.getStringValue(body, "position_code", "code")
|
|
positionCode = strings.TrimSpace(positionCode)
|
|
if positionCode == "" {
|
|
if !settings.AutoGenerateCodes {
|
|
c.jsonError(400, "职位编码不能为空")
|
|
return
|
|
}
|
|
positionCode = c.generateCode(settings.PositionCodePrefix, settings.CodeLength)
|
|
}
|
|
if !settings.AllowDuplicateCode {
|
|
count, err := c.positionQuery(tid).Filter("position_code", positionCode).Count()
|
|
if err != nil {
|
|
c.jsonError(500, "校验职位编码失败: "+err.Error())
|
|
return
|
|
}
|
|
if count > 0 {
|
|
c.jsonError(400, "职位编码已存在")
|
|
return
|
|
}
|
|
}
|
|
|
|
positionType, _ := c.getIntValue(body, "position_type")
|
|
status, hasStatus := c.getIntValue(body, "status")
|
|
sortVal, _ := c.getUintValue(body, "sort")
|
|
remark, _ := c.getStringValue(body, "remark")
|
|
|
|
row := models.BackendPosition{
|
|
Tid: tid,
|
|
DepartmentID: departmentID,
|
|
PositionCode: positionCode,
|
|
PositionName: positionName,
|
|
PositionType: int8(positionType),
|
|
Status: 1,
|
|
Sort: sortVal,
|
|
Remark: strPtrIfNotEmpty(remark),
|
|
}
|
|
if hasStatus {
|
|
row.Status = int8(status)
|
|
}
|
|
|
|
id, err := models.Orm.Insert(&row)
|
|
if err != nil {
|
|
c.jsonError(500, "创建职位失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
c.jsonOK(map[string]interface{}{"id": id, "position_code": positionCode})
|
|
}
|
|
|
|
// EditPosition 更新职位。
|
|
// POST /backend/{erp|oa}/editPosition/:id
|
|
func (c *BackendOrganizationController) EditPosition() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
id, valid := c.pathUint64(":id")
|
|
if !valid {
|
|
c.jsonError(400, "无效ID")
|
|
return
|
|
}
|
|
if !c.positionQuery(tid).Filter("id", id).Exist() {
|
|
c.jsonError(404, "职位不存在")
|
|
return
|
|
}
|
|
|
|
body := c.parseJSONBody()
|
|
settings := c.loadOrgSettings(tid)
|
|
update := orm.Params{}
|
|
|
|
if v, has := c.getUint64Value(body, "department_id"); has {
|
|
if v > 0 && !c.orgExists(tid, v) {
|
|
c.jsonError(400, "所属部门不存在")
|
|
return
|
|
}
|
|
update["department_id"] = v
|
|
}
|
|
if v, has := c.getStringValue(body, "position_code", "code"); has {
|
|
v = strings.TrimSpace(v)
|
|
if v == "" {
|
|
c.jsonError(400, "职位编码不能为空")
|
|
return
|
|
}
|
|
if !settings.AllowDuplicateCode {
|
|
count, err := c.positionQuery(tid).Filter("position_code", v).Exclude("id", id).Count()
|
|
if err != nil {
|
|
c.jsonError(500, "校验职位编码失败: "+err.Error())
|
|
return
|
|
}
|
|
if count > 0 {
|
|
c.jsonError(400, "职位编码已存在")
|
|
return
|
|
}
|
|
}
|
|
update["position_code"] = v
|
|
}
|
|
if v, has := c.getStringValue(body, "position_name", "name"); has {
|
|
v = strings.TrimSpace(v)
|
|
if v == "" {
|
|
c.jsonError(400, "职位名称不能为空")
|
|
return
|
|
}
|
|
update["position_name"] = v
|
|
}
|
|
if v, has := c.getIntValue(body, "position_type"); has {
|
|
update["position_type"] = int8(v)
|
|
}
|
|
if v, has := c.getIntValue(body, "status"); has {
|
|
update["status"] = int8(v)
|
|
}
|
|
if v, has := c.getUintValue(body, "sort"); has {
|
|
update["sort"] = v
|
|
}
|
|
if v, has := c.getStringValue(body, "remark"); has {
|
|
update["remark"] = nullableString(v)
|
|
}
|
|
|
|
if len(update) == 0 {
|
|
c.jsonError(400, "无更新字段")
|
|
return
|
|
}
|
|
|
|
if _, err := c.positionQuery(tid).Filter("id", id).Update(update); err != nil {
|
|
c.jsonError(500, "更新职位失败: "+err.Error())
|
|
return
|
|
}
|
|
|
|
c.jsonOK(nil)
|
|
}
|
|
|
|
// DeletePosition 删除职位(软删除)。
|
|
// DELETE /backend/{erp|oa}/deletePosition/:id
|
|
func (c *BackendOrganizationController) DeletePosition() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
id, valid := c.pathUint64(":id")
|
|
if !valid {
|
|
c.jsonError(400, "无效ID")
|
|
return
|
|
}
|
|
|
|
num, err := c.positionQuery(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)
|
|
}
|
|
|
|
// CheckPositionCodeUnique 检查职位编码唯一性。
|
|
// GET /backend/{erp|oa}/checkPositionCodeUnique?position_code=xx&exclude_id=1
|
|
func (c *BackendOrganizationController) CheckPositionCodeUnique() {
|
|
tid, ok := c.tenantID()
|
|
if !ok {
|
|
return
|
|
}
|
|
code := strings.TrimSpace(c.GetString("position_code"))
|
|
if code == "" {
|
|
c.jsonError(400, "职位编码不能为空")
|
|
return
|
|
}
|
|
excludeID, _ := c.GetUint64("exclude_id")
|
|
|
|
qs := c.positionQuery(tid).Filter("position_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, "编码")})
|
|
}
|