282 lines
7.9 KiB
Go
282 lines
7.9 KiB
Go
package services
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"golang.org/x/crypto/bcrypt"
|
|
"gorm.io/gorm"
|
|
|
|
"git.beifan.cn/trace-system/backend-go/database"
|
|
"git.beifan.cn/trace-system/backend-go/models"
|
|
)
|
|
|
|
// UsersService 用户管理服务
|
|
type UsersService struct{}
|
|
|
|
func toUserDTO(user models.User) models.UserDTO {
|
|
return models.UserDTO{
|
|
ID: user.ID,
|
|
Username: user.Username,
|
|
Name: user.Name,
|
|
Email: user.Email,
|
|
Phone: user.Phone,
|
|
EmployeeNo: user.EmployeeNo,
|
|
Position: user.Position,
|
|
Role: user.Role,
|
|
CreatedAt: user.CreatedAt,
|
|
EmployeeSerials: user.EmployeeSerials,
|
|
}
|
|
}
|
|
|
|
func hasBackendAccess(role string) bool {
|
|
return models.HasBackendAccess(role)
|
|
}
|
|
|
|
func isValidManagedRole(role string) bool {
|
|
return models.IsWorkOrderRole(role)
|
|
}
|
|
|
|
// Create 创建用户
|
|
func (s *UsersService) Create(dto models.CreateUserDTO) (*models.UserDTO, error) {
|
|
name := strings.TrimSpace(dto.Name)
|
|
phone := strings.TrimSpace(dto.Phone)
|
|
employeeNo := strings.TrimSpace(dto.EmployeeNo)
|
|
position := strings.TrimSpace(dto.Position)
|
|
role := strings.TrimSpace(dto.Role)
|
|
username := strings.TrimSpace(dto.Username)
|
|
if username == "" {
|
|
username = employeeNo
|
|
}
|
|
|
|
if name == "" {
|
|
return nil, errors.New("姓名不能为空")
|
|
}
|
|
if phone == "" {
|
|
return nil, errors.New("电话不能为空")
|
|
}
|
|
if employeeNo == "" {
|
|
return nil, errors.New("工号不能为空")
|
|
}
|
|
if position == "" {
|
|
return nil, errors.New("岗位不能为空")
|
|
}
|
|
if !isValidManagedRole(role) {
|
|
return nil, errors.New("权限管理只能创建软件工程师、硬件工程师、商务经理、项目经理")
|
|
}
|
|
if hasBackendAccess(role) && len(dto.Password) < 6 {
|
|
return nil, errors.New("工单处理账号必须设置至少 6 位初始密码")
|
|
}
|
|
|
|
var existing models.User
|
|
err := database.DB.Where("username = ?", username).First(&existing).Error
|
|
if err == nil {
|
|
return nil, errors.New("用户名已存在")
|
|
}
|
|
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, fmt.Errorf("查询用户失败: %w", err)
|
|
}
|
|
|
|
err = database.DB.Where("employee_no = ?", employeeNo).First(&existing).Error
|
|
if err == nil {
|
|
return nil, errors.New("工号已存在")
|
|
}
|
|
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, fmt.Errorf("查询员工工号失败: %w", err)
|
|
}
|
|
|
|
hashed := ""
|
|
if hasBackendAccess(role) {
|
|
hashBytes, err := bcrypt.GenerateFromPassword([]byte(dto.Password), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("密码加密失败: %w", err)
|
|
}
|
|
hashed = string(hashBytes)
|
|
}
|
|
|
|
user := models.User{
|
|
Username: username,
|
|
Password: hashed,
|
|
Name: name,
|
|
Email: dto.Email,
|
|
Phone: phone,
|
|
EmployeeNo: employeeNo,
|
|
Position: position,
|
|
Role: role,
|
|
}
|
|
|
|
err = database.DB.Transaction(func(tx *gorm.DB) error {
|
|
if err := tx.Create(&user).Error; err != nil {
|
|
return fmt.Errorf("创建员工失败: %w", err)
|
|
}
|
|
|
|
serialService := EmployeeSerialsService{}
|
|
if _, err := serialService.GenerateForEmployee(tx, user, user.ID, ""); err != nil {
|
|
return err
|
|
}
|
|
|
|
return tx.Preload("EmployeeSerials").First(&user, user.ID).Error
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
dtoOut := toUserDTO(user)
|
|
return &dtoOut, nil
|
|
}
|
|
|
|
// FindAll 分页 + 按角色过滤
|
|
func (s *UsersService) FindAll(page int, limit int, role string, search string) ([]models.UserDTO, int, int, error) {
|
|
var users []models.User
|
|
var total int64
|
|
|
|
db := database.DB.Model(&models.User{}).Preload("EmployeeSerials")
|
|
if role != "" {
|
|
if !models.IsAssignableWorkOrderRole(role) {
|
|
return []models.UserDTO{}, 0, 0, nil
|
|
}
|
|
db = db.Where("role = ?", role)
|
|
} else {
|
|
db = db.Where("role IN ?", models.AssignableWorkOrderRoles)
|
|
}
|
|
if search != "" {
|
|
pattern := "%" + search + "%"
|
|
db = db.Where("username LIKE ? OR name LIKE ? OR email LIKE ? OR phone LIKE ? OR employee_no LIKE ? OR position LIKE ?",
|
|
pattern, pattern, pattern, pattern, pattern, pattern)
|
|
}
|
|
|
|
if err := db.Count(&total).Error; err != nil {
|
|
return nil, 0, 0, fmt.Errorf("统计用户数失败: %w", err)
|
|
}
|
|
|
|
offset := (page - 1) * limit
|
|
if err := db.Order("created_at DESC").Offset(offset).Limit(limit).Find(&users).Error; err != nil {
|
|
return nil, 0, 0, fmt.Errorf("查询用户列表失败: %w", err)
|
|
}
|
|
|
|
result := make([]models.UserDTO, 0, len(users))
|
|
for _, u := range users {
|
|
result = append(result, toUserDTO(u))
|
|
}
|
|
|
|
totalPages := (int(total) + limit - 1) / limit
|
|
return result, int(total), totalPages, nil
|
|
}
|
|
|
|
// FindAssignable 获取可分配的工单处理人员,用于工单分配
|
|
func (s *UsersService) FindAssignable() ([]models.UserDTO, error) {
|
|
var users []models.User
|
|
if err := database.DB.Where("role IN ?", models.AssignableWorkOrderRoles).
|
|
Order("created_at ASC").Find(&users).Error; err != nil {
|
|
return nil, fmt.Errorf("查询可分配用户失败: %w", err)
|
|
}
|
|
result := make([]models.UserDTO, 0, len(users))
|
|
for _, u := range users {
|
|
result = append(result, toUserDTO(u))
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// Update 更新用户信息(不含密码)
|
|
func (s *UsersService) Update(userId uint, dto models.UpdateUserDTO, currentUserId uint) (*models.UserDTO, error) {
|
|
var user models.User
|
|
if err := database.DB.First(&user, userId).Error; err != nil {
|
|
return nil, errors.New("用户不存在")
|
|
}
|
|
|
|
if dto.Name != "" {
|
|
user.Name = strings.TrimSpace(dto.Name)
|
|
}
|
|
if dto.Email != "" {
|
|
user.Email = dto.Email
|
|
}
|
|
if dto.Phone != "" {
|
|
user.Phone = strings.TrimSpace(dto.Phone)
|
|
}
|
|
if dto.EmployeeNo != "" {
|
|
employeeNo := strings.TrimSpace(dto.EmployeeNo)
|
|
var existing models.User
|
|
err := database.DB.Where("employee_no = ? AND id <> ?", employeeNo, userId).First(&existing).Error
|
|
if err == nil {
|
|
return nil, errors.New("工号已存在")
|
|
}
|
|
if !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, fmt.Errorf("查询员工工号失败: %w", err)
|
|
}
|
|
user.EmployeeNo = employeeNo
|
|
user.Username = employeeNo
|
|
}
|
|
if dto.Position != "" {
|
|
user.Position = strings.TrimSpace(dto.Position)
|
|
}
|
|
if dto.Role != "" {
|
|
if !isValidManagedRole(dto.Role) {
|
|
return nil, errors.New("权限管理只能设置软件工程师、硬件工程师、商务经理、项目经理")
|
|
}
|
|
if user.Role == "admin" {
|
|
return nil, errors.New("不能通过权限管理修改管理员角色")
|
|
}
|
|
// 防止管理员把自己降级
|
|
if user.ID == currentUserId && user.Role == "admin" && dto.Role != "admin" {
|
|
return nil, errors.New("不能修改自己的管理员角色")
|
|
}
|
|
user.Role = dto.Role
|
|
}
|
|
|
|
if err := database.DB.Save(&user).Error; err != nil {
|
|
return nil, fmt.Errorf("更新用户失败: %w", err)
|
|
}
|
|
|
|
dtoOut := toUserDTO(user)
|
|
return &dtoOut, nil
|
|
}
|
|
|
|
// ResetPassword 管理员重置用户密码
|
|
func (s *UsersService) ResetPassword(userId uint, newPassword string) error {
|
|
var user models.User
|
|
if err := database.DB.First(&user, userId).Error; err != nil {
|
|
return errors.New("用户不存在")
|
|
}
|
|
if !hasBackendAccess(user.Role) {
|
|
return errors.New("该账号无后台登录权限,不能设置密码")
|
|
}
|
|
|
|
hashed, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
|
|
if err != nil {
|
|
return fmt.Errorf("密码加密失败: %w", err)
|
|
}
|
|
|
|
user.Password = string(hashed)
|
|
if err := database.DB.Save(&user).Error; err != nil {
|
|
return fmt.Errorf("重置密码失败: %w", err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Delete 删除用户
|
|
func (s *UsersService) Delete(userId uint, currentUserId uint) error {
|
|
if userId == currentUserId {
|
|
return errors.New("不能删除自己")
|
|
}
|
|
|
|
var user models.User
|
|
if err := database.DB.First(&user, userId).Error; err != nil {
|
|
return errors.New("用户不存在")
|
|
}
|
|
|
|
if user.Role == "admin" {
|
|
// 防止删除最后一个 admin
|
|
var adminCount int64
|
|
database.DB.Model(&models.User{}).Where("role = ?", "admin").Count(&adminCount)
|
|
if adminCount <= 1 {
|
|
return errors.New("不能删除最后一个管理员")
|
|
}
|
|
}
|
|
|
|
if err := database.DB.Delete(&user).Error; err != nil {
|
|
return fmt.Errorf("删除用户失败: %w", err)
|
|
}
|
|
return nil
|
|
}
|