Files
yunzerwebsiteallinone/go/services/employee_sync.go
T
2026-11-13 20:26:16 +08:00

380 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}