Files
yunzerwebsiteallinone/go/controllers/backend_organization.go
T
2026-08-27 21:25:39 +08:00

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, "编码")})
}