380 lines
11 KiB
Go
380 lines
11 KiB
Go
package services
|
||
|
||
import (
|
||
"fmt"
|
||
"server/models"
|
||
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/beego/beego/v2/client/orm"
|
||
)
|
||
|
||
// OnEmployeeCreatedHook 员工创建后的外部回调(由 controllers 包在初始化时注册,如同步通讯录)
|
||
var OnEmployeeCreatedHook func(tid uint64, employeeID uint64, emp *models.BackendEmployee)
|
||
|
||
// parseDate 解析日期字符串为 *time.Time
|
||
func parseDate(v string) *time.Time {
|
||
v = strings.TrimSpace(v)
|
||
if v == "" {
|
||
return nil
|
||
}
|
||
if t, err := time.Parse("2006-01-02", v); err == nil {
|
||
return &t
|
||
}
|
||
if t, err := time.Parse("2006-01-02 15:04:05", v); err == nil {
|
||
return &t
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// EnsureEmployeeForTenantUser 确保租户用户在 yz_backend_employee 中有对应的人员记录。
|
||
// 规则:先有用户,后自动生成员工,以 (tid, account) 严格匹配。
|
||
func EnsureEmployeeForTenantUser(tu *models.SystemTenantUser) (*models.BackendEmployee, bool, error) {
|
||
if tu == nil || tu.Account == nil {
|
||
return nil, false, nil
|
||
}
|
||
account := strings.TrimSpace(*tu.Account)
|
||
if account == "" {
|
||
return nil, false, nil
|
||
}
|
||
tid := int(tu.Tid)
|
||
if tid <= 0 {
|
||
return nil, false, nil
|
||
}
|
||
|
||
var emp models.BackendEmployee
|
||
// 员工表的 uk_account 是全局唯一索引(不是 tid+account 复合索引)。
|
||
// 因此先按账号查全表,避免已有的历史/其他租户同账号记录被误判为缺失后再 INSERT。
|
||
err := models.Orm.QueryTable(new(models.BackendEmployee)).
|
||
Filter("account", account).
|
||
One(&emp)
|
||
if err == nil && emp.Tid != nil && *emp.Tid > 0 && *emp.Tid != tid {
|
||
return nil, false, fmt.Errorf("员工账号 %s 已被其他租户使用,不能重复创建", account)
|
||
}
|
||
|
||
name := account
|
||
if tu.Name != nil && strings.TrimSpace(*tu.Name) != "" {
|
||
name = strings.TrimSpace(*tu.Name)
|
||
}
|
||
|
||
var birthday *time.Time
|
||
if tu.Birth != nil && strings.TrimSpace(*tu.Birth) != "" {
|
||
birthday = parseDate(strings.TrimSpace(*tu.Birth))
|
||
}
|
||
|
||
status := int8(1)
|
||
if tu.Status == 0 {
|
||
status = 0
|
||
}
|
||
|
||
var deptPtr *string
|
||
if tu.OrgID > 0 {
|
||
deptStr := strconv.FormatUint(tu.OrgID, 10)
|
||
deptPtr = &deptStr
|
||
}
|
||
|
||
if err == orm.ErrNoRows {
|
||
// 不存在 -> 自动创建新员工
|
||
newEmp := models.BackendEmployee{
|
||
Tid: &tid,
|
||
Account: account,
|
||
Name: name,
|
||
Gender: int8(tu.Sex),
|
||
Birthday: birthday,
|
||
Department: deptPtr,
|
||
Phone: tu.Phone,
|
||
Email: tu.Email,
|
||
AccountStatus: status,
|
||
}
|
||
id, insertErr := models.Orm.Insert(&newEmp)
|
||
if insertErr != nil {
|
||
return nil, false, insertErr
|
||
}
|
||
newEmp.ID = uint(id)
|
||
|
||
if OnEmployeeCreatedHook != nil {
|
||
OnEmployeeCreatedHook(tu.Tid, uint64(newEmp.ID), &newEmp)
|
||
}
|
||
|
||
return &newEmp, true, nil
|
||
} else if err != nil {
|
||
return nil, false, err
|
||
}
|
||
|
||
// 已存在 -> 只同步用户侧明确提供的基础资料;不覆盖部门、职位及其他员工专属字段。
|
||
update := orm.Params{}
|
||
if emp.Tid == nil || *emp.Tid <= 0 {
|
||
// 历史无租户归属的员工记录:被当前租户用户明确匹配后,补上租户归属。
|
||
update["tid"] = tid
|
||
}
|
||
if emp.DeleteTime != nil {
|
||
update["delete_time"] = nil
|
||
}
|
||
if emp.Name != name {
|
||
update["name"] = name
|
||
}
|
||
if emp.Gender != int8(tu.Sex) {
|
||
update["gender"] = int8(tu.Sex)
|
||
}
|
||
if birthday != nil && (emp.Birthday == nil || !emp.Birthday.Equal(*birthday)) {
|
||
update["birthday"] = birthday
|
||
}
|
||
if tu.Phone != nil && strings.TrimSpace(*tu.Phone) != "" && (emp.Phone == nil || *emp.Phone != *tu.Phone) {
|
||
update["phone"] = *tu.Phone
|
||
}
|
||
if tu.Email != nil && strings.TrimSpace(*tu.Email) != "" && (emp.Email == nil || *emp.Email != *tu.Email) {
|
||
update["email"] = *tu.Email
|
||
}
|
||
if tu.OrgID > 0 && (emp.Department == nil || *emp.Department == "") {
|
||
update["department"] = *deptPtr
|
||
}
|
||
if emp.AccountStatus != status && emp.AccountStatus != 2 {
|
||
update["account_status"] = status
|
||
}
|
||
if len(update) > 0 {
|
||
update["update_time"] = time.Now()
|
||
query := models.Orm.QueryTable(new(models.BackendEmployee)).Filter("id", emp.ID)
|
||
if emp.Tid != nil && *emp.Tid > 0 {
|
||
query = query.Filter("tid", tid)
|
||
}
|
||
if _, err := query.Update(update); err != nil {
|
||
return nil, false, err
|
||
}
|
||
}
|
||
|
||
return &emp, false, nil
|
||
}
|
||
|
||
// SyncSelectedTenantUsersToEmployees 对指定租户用户进行比对;仅对缺失或基础资料有差异的员工执行写入。
|
||
func SyncSelectedTenantUsersToEmployees(tid uint64, userIDs []uint64) (map[string]interface{}, error) {
|
||
counts := map[string]interface{}{"created": 0, "updated": 0, "unchanged": 0, "failed": 0, "errors": []string{}}
|
||
if tid == 0 || len(userIDs) == 0 {
|
||
return counts, nil
|
||
}
|
||
seen := make(map[uint64]struct{}, len(userIDs))
|
||
for _, userID := range userIDs {
|
||
if userID == 0 {
|
||
counts["failed"] = counts["failed"].(int) + 1
|
||
counts["errors"] = append(counts["errors"].([]string), "用户ID无效")
|
||
continue
|
||
}
|
||
if _, duplicate := seen[userID]; duplicate {
|
||
continue
|
||
}
|
||
seen[userID] = struct{}{}
|
||
|
||
var user models.SystemTenantUser
|
||
err := models.Orm.QueryTable(new(models.SystemTenantUser)).
|
||
Filter("id", userID).
|
||
Filter("tid", tid).
|
||
Filter("delete_time__isnull", true).
|
||
One(&user)
|
||
if err != nil {
|
||
counts["failed"] = counts["failed"].(int) + 1
|
||
counts["errors"] = append(counts["errors"].([]string), fmt.Sprintf("用户记录 %d 不存在于当前租户或已删除", userID))
|
||
continue
|
||
}
|
||
|
||
differs := tenantUserEmployeeDiffers(&user)
|
||
_, created, err := EnsureEmployeeForTenantUser(&user)
|
||
if err != nil {
|
||
counts["failed"] = counts["failed"].(int) + 1
|
||
account := ""
|
||
if user.Account != nil {
|
||
account = strings.TrimSpace(*user.Account)
|
||
}
|
||
counts["errors"] = append(counts["errors"].([]string), fmt.Sprintf("用户 %s(ID %d)同步失败: %v", account, userID, err))
|
||
continue
|
||
}
|
||
if created {
|
||
counts["created"] = counts["created"].(int) + 1
|
||
} else if differs {
|
||
counts["updated"] = counts["updated"].(int) + 1
|
||
} else {
|
||
counts["unchanged"] = counts["unchanged"].(int) + 1
|
||
}
|
||
}
|
||
return counts, nil
|
||
}
|
||
|
||
func tenantUserEmployeeDiffers(user *models.SystemTenantUser) bool {
|
||
if user == nil || user.Account == nil {
|
||
return true
|
||
}
|
||
var employee models.BackendEmployee
|
||
if err := models.Orm.QueryTable(new(models.BackendEmployee)).
|
||
Filter("tid", int(user.Tid)).
|
||
Filter("account", strings.TrimSpace(*user.Account)).
|
||
Filter("delete_time__isnull", true).
|
||
One(&employee); err != nil {
|
||
return true
|
||
}
|
||
name := strings.TrimSpace(*user.Account)
|
||
if user.Name != nil && strings.TrimSpace(*user.Name) != "" {
|
||
name = strings.TrimSpace(*user.Name)
|
||
}
|
||
status := int8(1)
|
||
if user.Status == 0 {
|
||
status = 0
|
||
}
|
||
if employee.Name != name || employeeStatusDiffers(employee.AccountStatus, status) || int(employee.Gender) != int(user.Sex) {
|
||
return true
|
||
}
|
||
if user.Phone != nil && (employee.Phone == nil || *employee.Phone != *user.Phone) {
|
||
return true
|
||
}
|
||
if user.Email != nil && (employee.Email == nil || *employee.Email != *user.Email) {
|
||
return true
|
||
}
|
||
if user.Birth != nil && strings.TrimSpace(*user.Birth) != "" {
|
||
birthday := parseDate(*user.Birth)
|
||
if birthday != nil && (employee.Birthday == nil || !employee.Birthday.Equal(*birthday)) {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
func employeeStatusDiffers(employeeStatus, userStatus int8) bool {
|
||
if employeeStatus == 2 {
|
||
return false // 离职员工由人事管理维护,不通过用户同步自动复职
|
||
}
|
||
return employeeStatus != userStatus
|
||
}
|
||
|
||
// SyncEmployeeOnUserUpdate 当用户在用户管理中被编辑时,同步修改对应员工
|
||
func SyncEmployeeOnUserUpdate(tid uint64, oldAccount string, tu *models.SystemTenantUser) error {
|
||
if tu == nil || tid == 0 {
|
||
return nil
|
||
}
|
||
acc := oldAccount
|
||
if acc == "" && tu.Account != nil {
|
||
acc = *tu.Account
|
||
}
|
||
acc = strings.TrimSpace(acc)
|
||
if acc == "" {
|
||
return nil
|
||
}
|
||
|
||
var emp models.BackendEmployee
|
||
err := models.Orm.QueryTable(new(models.BackendEmployee)).
|
||
Filter("tid", int(tid)).
|
||
Filter("account", acc).
|
||
One(&emp)
|
||
if err != nil {
|
||
// 员工不存在则自动创建
|
||
_, _, err = EnsureEmployeeForTenantUser(tu)
|
||
return err
|
||
}
|
||
|
||
changed := false
|
||
if tu.Account != nil && strings.TrimSpace(*tu.Account) != "" && emp.Account != strings.TrimSpace(*tu.Account) {
|
||
emp.Account = strings.TrimSpace(*tu.Account)
|
||
changed = true
|
||
}
|
||
if tu.Name != nil && strings.TrimSpace(*tu.Name) != "" && emp.Name != strings.TrimSpace(*tu.Name) {
|
||
emp.Name = strings.TrimSpace(*tu.Name)
|
||
changed = true
|
||
}
|
||
if tu.Sex > 0 && emp.Gender != int8(tu.Sex) {
|
||
emp.Gender = int8(tu.Sex)
|
||
changed = true
|
||
}
|
||
if tu.Birth != nil {
|
||
tb := parseDate(strings.TrimSpace(*tu.Birth))
|
||
if tb != nil && (emp.Birthday == nil || !emp.Birthday.Equal(*tb)) {
|
||
emp.Birthday = tb
|
||
changed = true
|
||
}
|
||
}
|
||
if tu.Phone != nil && (emp.Phone == nil || *emp.Phone != *tu.Phone) {
|
||
emp.Phone = tu.Phone
|
||
changed = true
|
||
}
|
||
if tu.Email != nil && (emp.Email == nil || *emp.Email != *tu.Email) {
|
||
emp.Email = tu.Email
|
||
changed = true
|
||
}
|
||
if tu.Status == 1 && emp.AccountStatus == 0 {
|
||
emp.AccountStatus = 1
|
||
changed = true
|
||
} else if tu.Status == 0 && emp.AccountStatus == 1 {
|
||
emp.AccountStatus = 0
|
||
changed = true
|
||
}
|
||
|
||
if changed {
|
||
emp.UpdateTime = time.Now()
|
||
_, err = models.Orm.Update(&emp)
|
||
return err
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// SyncEmployeeOnUserDelete 当租户用户被删除时,将员工置为离职状态(2)
|
||
func SyncEmployeeOnUserDelete(tid uint64, account string) error {
|
||
account = strings.TrimSpace(account)
|
||
if tid == 0 || account == "" {
|
||
return nil
|
||
}
|
||
_, err := models.Orm.QueryTable(new(models.BackendEmployee)).
|
||
Filter("tid", int(tid)).
|
||
Filter("account", account).
|
||
Filter("delete_time__isnull", true).
|
||
Update(orm.Params{
|
||
"account_status": int8(2), // 2 = 离职
|
||
"update_time": time.Now(),
|
||
})
|
||
return err
|
||
}
|
||
|
||
// SyncUserFromEmployee 当在人员管理中编辑了员工的基本信息时,同步回用户表
|
||
func SyncUserFromEmployee(tid uint64, emp *models.BackendEmployee) error {
|
||
if emp == nil || tid == 0 {
|
||
return nil
|
||
}
|
||
account := strings.TrimSpace(emp.Account)
|
||
if account == "" {
|
||
return nil
|
||
}
|
||
|
||
update := orm.Params{
|
||
"update_time": time.Now(),
|
||
}
|
||
if emp.Name != "" {
|
||
update["name"] = emp.Name
|
||
}
|
||
if emp.Phone != nil {
|
||
update["phone"] = *emp.Phone
|
||
}
|
||
if emp.Email != nil {
|
||
update["email"] = *emp.Email
|
||
}
|
||
if emp.Gender > 0 {
|
||
update["sex"] = uint8(emp.Gender)
|
||
}
|
||
if emp.Birthday != nil {
|
||
update["birth"] = emp.Birthday.Format("2006-01-02")
|
||
}
|
||
if emp.Department != nil && *emp.Department != "" {
|
||
if orgId, err := strconv.ParseUint(*emp.Department, 10, 64); err == nil && orgId > 0 {
|
||
update["org_id"] = orgId
|
||
}
|
||
}
|
||
// 员工状态 1: 启用 -> 用户 1; 员工状态 0: 禁用 / 2: 离职 -> 用户 0 (禁用登录)
|
||
if emp.AccountStatus == 1 {
|
||
update["status"] = int8(1)
|
||
} else {
|
||
update["status"] = int8(0)
|
||
}
|
||
|
||
_, err := models.Orm.QueryTable(new(models.SystemTenantUser)).
|
||
Filter("tid", tid).
|
||
Filter("account", account).
|
||
Filter("delete_time__isnull", true).
|
||
Update(update)
|
||
return err
|
||
}
|