Refactor employee management
This commit is contained in:
+24
-12
@@ -23,6 +23,12 @@ func (s *AuthService) ValidateUser(username string, password string) (*models.Us
|
||||
if result.Error != nil {
|
||||
return nil, errors.New("用户名或密码不正确")
|
||||
}
|
||||
if user.Role != "admin" && user.Role != "technician" {
|
||||
return nil, errors.New("用户名或密码不正确")
|
||||
}
|
||||
if user.Password == "" {
|
||||
return nil, errors.New("用户名或密码不正确")
|
||||
}
|
||||
|
||||
err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
|
||||
if err != nil {
|
||||
@@ -56,12 +62,15 @@ func (s *AuthService) GetProfile(userId uint) (*models.UserDTO, error) {
|
||||
}
|
||||
|
||||
return &models.UserDTO{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Name: user.Name,
|
||||
Email: user.Email,
|
||||
Role: user.Role,
|
||||
CreatedAt: user.CreatedAt,
|
||||
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,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -109,11 +118,14 @@ func (s *AuthService) UpdateProfile(userId uint, name string, email string) (*mo
|
||||
}
|
||||
|
||||
return &models.UserDTO{
|
||||
ID: user.ID,
|
||||
Username: user.Username,
|
||||
Name: user.Name,
|
||||
Email: user.Email,
|
||||
Role: user.Role,
|
||||
CreatedAt: user.CreatedAt,
|
||||
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,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -16,11 +16,14 @@ import (
|
||||
|
||||
"git.beifan.cn/trace-system/backend-go/database"
|
||||
"git.beifan.cn/trace-system/backend-go/models"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// EmployeeSerialsService 员工序列号服务
|
||||
type EmployeeSerialsService struct{}
|
||||
|
||||
const defaultEmployeeCompanyName = "内部员工"
|
||||
|
||||
// Generate 生成员工序列号
|
||||
func (s *EmployeeSerialsService) Generate(
|
||||
companyName string,
|
||||
@@ -29,18 +32,62 @@ func (s *EmployeeSerialsService) Generate(
|
||||
quantity int,
|
||||
userId uint,
|
||||
serialPrefix string,
|
||||
) ([]models.EmployeeSerial, error) {
|
||||
return s.generateWithDB(database.DB, companyName, position, employeeName, quantity, userId, nil, serialPrefix)
|
||||
}
|
||||
|
||||
// GenerateForEmployee 为员工主档生成一个员工码
|
||||
func (s *EmployeeSerialsService) GenerateForEmployee(
|
||||
tx *gorm.DB,
|
||||
employee models.User,
|
||||
creatorID uint,
|
||||
serialPrefix string,
|
||||
) (*models.EmployeeSerial, error) {
|
||||
db := tx
|
||||
if db == nil {
|
||||
db = database.DB
|
||||
}
|
||||
|
||||
serials, err := s.generateWithDB(
|
||||
db,
|
||||
defaultEmployeeCompanyName,
|
||||
employee.Position,
|
||||
employee.Name,
|
||||
1,
|
||||
creatorID,
|
||||
&employee.ID,
|
||||
serialPrefix,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(serials) == 0 {
|
||||
return nil, errors.New("员工码生成失败")
|
||||
}
|
||||
return &serials[0], nil
|
||||
}
|
||||
|
||||
func (s *EmployeeSerialsService) generateWithDB(
|
||||
db *gorm.DB,
|
||||
companyName string,
|
||||
position string,
|
||||
employeeName string,
|
||||
quantity int,
|
||||
userId uint,
|
||||
employeeID *uint,
|
||||
serialPrefix string,
|
||||
) ([]models.EmployeeSerial, error) {
|
||||
var serials []models.EmployeeSerial
|
||||
|
||||
// 检查公司是否存在,不存在则创建
|
||||
var company models.Company
|
||||
result := database.DB.Where("company_name = ?", companyName).First(&company)
|
||||
result := db.Where("company_name = ?", companyName).First(&company)
|
||||
if result.Error != nil {
|
||||
company = models.Company{
|
||||
CompanyName: companyName,
|
||||
IsActive: true,
|
||||
}
|
||||
result = database.DB.Create(&company)
|
||||
result = db.Create(&company)
|
||||
if result.Error != nil {
|
||||
return nil, fmt.Errorf("创建公司失败: %w", result.Error)
|
||||
}
|
||||
@@ -68,7 +115,7 @@ func (s *EmployeeSerialsService) Generate(
|
||||
}
|
||||
|
||||
var existingSerial models.EmployeeSerial
|
||||
checkResult := database.DB.Where("serial_number = ?", serialNumber).First(&existingSerial)
|
||||
checkResult := db.Where("serial_number = ?", serialNumber).First(&existingSerial)
|
||||
if checkResult.Error != nil {
|
||||
serialNumbers[serialNumber] = true
|
||||
i++
|
||||
@@ -81,6 +128,7 @@ func (s *EmployeeSerialsService) Generate(
|
||||
CompanyName: companyName,
|
||||
Position: position,
|
||||
EmployeeName: employeeName,
|
||||
EmployeeID: employeeID,
|
||||
CreatedBy: &userId,
|
||||
IsActive: true,
|
||||
}
|
||||
@@ -88,7 +136,7 @@ func (s *EmployeeSerialsService) Generate(
|
||||
}
|
||||
|
||||
// 保存到数据库
|
||||
result = database.DB.Create(&serials)
|
||||
result = db.Create(&serials)
|
||||
if result.Error != nil {
|
||||
return nil, fmt.Errorf("保存员工序列号失败: %w", result.Error)
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ func TestAuthService_ValidateUser_Success(t *testing.T) {
|
||||
Password: string(password),
|
||||
Name: "测试用户",
|
||||
Email: "test@example.com",
|
||||
Role: "user",
|
||||
Role: "technician",
|
||||
}
|
||||
database.DB.Create(&user)
|
||||
|
||||
@@ -75,7 +75,7 @@ func TestAuthService_ValidateUser_WrongPassword(t *testing.T) {
|
||||
Password: string(password),
|
||||
Name: "测试用户2",
|
||||
Email: "test2@example.com",
|
||||
Role: "user",
|
||||
Role: "technician",
|
||||
}
|
||||
database.DB.Create(&user)
|
||||
|
||||
@@ -87,6 +87,25 @@ func TestAuthService_ValidateUser_WrongPassword(t *testing.T) {
|
||||
database.DB.Unscoped().Delete(&user)
|
||||
}
|
||||
|
||||
func TestAuthService_ValidateUser_EmployeeCannotLogin(t *testing.T) {
|
||||
password, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
|
||||
user := models.User{
|
||||
Username: "employee_no_login",
|
||||
Password: string(password),
|
||||
Name: "普通员工",
|
||||
Role: "employee",
|
||||
}
|
||||
database.DB.Create(&user)
|
||||
|
||||
authService := AuthService{}
|
||||
_, err := authService.ValidateUser("employee_no_login", "password123")
|
||||
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "用户名或密码不正确")
|
||||
|
||||
database.DB.Unscoped().Delete(&user)
|
||||
}
|
||||
|
||||
func TestAuthService_ValidateUser_UserNotFound(t *testing.T) {
|
||||
authService := AuthService{}
|
||||
_, err := authService.ValidateUser("nonexistent", "password")
|
||||
@@ -98,7 +117,7 @@ func TestAuthService_GenerateToken_Success(t *testing.T) {
|
||||
user := &models.User{
|
||||
ID: 1,
|
||||
Username: "testuser",
|
||||
Role: "user",
|
||||
Role: "technician",
|
||||
}
|
||||
|
||||
authService := AuthService{}
|
||||
@@ -117,7 +136,7 @@ func TestAuthService_GetProfile_Success(t *testing.T) {
|
||||
Password: string(password),
|
||||
Name: "测试用户3",
|
||||
Email: "test3@example.com",
|
||||
Role: "user",
|
||||
Role: "technician",
|
||||
}
|
||||
database.DB.Create(&user)
|
||||
|
||||
@@ -140,7 +159,7 @@ func TestAuthService_ChangePassword_Success(t *testing.T) {
|
||||
Password: string(password),
|
||||
Name: "测试用户4",
|
||||
Email: "test4@example.com",
|
||||
Role: "user",
|
||||
Role: "technician",
|
||||
}
|
||||
database.DB.Create(&user)
|
||||
|
||||
@@ -165,7 +184,7 @@ func TestAuthService_ChangePassword_WrongCurrentPassword(t *testing.T) {
|
||||
Password: string(password),
|
||||
Name: "测试用户5",
|
||||
Email: "test5@example.com",
|
||||
Role: "user",
|
||||
Role: "technician",
|
||||
}
|
||||
database.DB.Create(&user)
|
||||
|
||||
@@ -185,7 +204,7 @@ func TestAuthService_UpdateProfile_Success(t *testing.T) {
|
||||
Password: string(password),
|
||||
Name: "测试用户6",
|
||||
Email: "test6@example.com",
|
||||
Role: "user",
|
||||
Role: "technician",
|
||||
}
|
||||
database.DB.Create(&user)
|
||||
|
||||
|
||||
+111
-19
@@ -17,18 +17,57 @@ 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,
|
||||
Role: user.Role,
|
||||
CreatedAt: user.CreatedAt,
|
||||
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 role == "admin" || role == "technician"
|
||||
}
|
||||
|
||||
func isValidEmployeeRole(role string) bool {
|
||||
return role == "admin" || role == "technician" || role == "employee"
|
||||
}
|
||||
|
||||
// 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 !isValidEmployeeRole(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
|
||||
@@ -39,21 +78,48 @@ func (s *UsersService) Create(dto models.CreateUserDTO) (*models.UserDTO, error)
|
||||
return nil, fmt.Errorf("查询用户失败: %w", err)
|
||||
}
|
||||
|
||||
hashed, err := bcrypt.GenerateFromPassword([]byte(dto.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
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: string(hashed),
|
||||
Name: dto.Name,
|
||||
Email: dto.Email,
|
||||
Role: dto.Role,
|
||||
Username: username,
|
||||
Password: hashed,
|
||||
Name: name,
|
||||
Email: dto.Email,
|
||||
Phone: phone,
|
||||
EmployeeNo: employeeNo,
|
||||
Position: position,
|
||||
Role: role,
|
||||
}
|
||||
|
||||
if err := database.DB.Create(&user).Error; err != nil {
|
||||
return nil, fmt.Errorf("创建用户失败: %w", err)
|
||||
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)
|
||||
@@ -65,13 +131,14 @@ func (s *UsersService) FindAll(page int, limit int, role string, search string)
|
||||
var users []models.User
|
||||
var total int64
|
||||
|
||||
db := database.DB.Model(&models.User{})
|
||||
db := database.DB.Model(&models.User{}).Preload("EmployeeSerials")
|
||||
if role != "" {
|
||||
db = db.Where("role = ?", role)
|
||||
}
|
||||
if search != "" {
|
||||
pattern := "%" + search + "%"
|
||||
db = db.Where("username LIKE ? OR name LIKE ? OR email LIKE ?", pattern, pattern, pattern)
|
||||
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 {
|
||||
@@ -114,12 +181,34 @@ func (s *UsersService) Update(userId uint, dto models.UpdateUserDTO, currentUser
|
||||
}
|
||||
|
||||
if dto.Name != "" {
|
||||
user.Name = 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 !isValidEmployeeRole(dto.Role) {
|
||||
return nil, errors.New("角色不正确")
|
||||
}
|
||||
// 防止管理员把自己降级
|
||||
if user.ID == currentUserId && user.Role == "admin" && dto.Role != "admin" {
|
||||
return nil, errors.New("不能修改自己的管理员角色")
|
||||
@@ -141,6 +230,9 @@ func (s *UsersService) ResetPassword(userId uint, newPassword string) error {
|
||||
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 {
|
||||
|
||||
@@ -13,18 +13,59 @@ import (
|
||||
func TestUsersService_Create_Success(t *testing.T) {
|
||||
svc := UsersService{}
|
||||
dto, err := svc.Create(models.CreateUserDTO{
|
||||
Username: "users_create_ok",
|
||||
Password: "password123",
|
||||
Name: "新技术员",
|
||||
Email: "new@example.com",
|
||||
Role: "technician",
|
||||
Password: "password123",
|
||||
Name: "新技术员",
|
||||
Email: "new@example.com",
|
||||
Phone: "13800000001",
|
||||
EmployeeNo: "users_create_ok",
|
||||
Position: "技术员",
|
||||
Role: "technician",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, dto)
|
||||
assert.Equal(t, "users_create_ok", dto.Username)
|
||||
assert.Equal(t, "technician", dto.Role)
|
||||
assert.Equal(t, "13800000001", dto.Phone)
|
||||
assert.Equal(t, "users_create_ok", dto.EmployeeNo)
|
||||
assert.Len(t, dto.EmployeeSerials, 1)
|
||||
|
||||
database.DB.Unscoped().Where("username = ?", "users_create_ok").Delete(&models.User{})
|
||||
database.DB.Unscoped().Where("employee_name = ?", "新技术员").Delete(&models.EmployeeSerial{})
|
||||
}
|
||||
|
||||
func TestUsersService_Create_EmployeeWithoutPasswordGeneratesSerial(t *testing.T) {
|
||||
svc := UsersService{}
|
||||
dto, err := svc.Create(models.CreateUserDTO{
|
||||
Name: "普通员工",
|
||||
Phone: "13800000002",
|
||||
EmployeeNo: "employee_no_pwd",
|
||||
Position: "生产员工",
|
||||
Role: "employee",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, dto)
|
||||
assert.Equal(t, "employee", dto.Role)
|
||||
assert.Len(t, dto.EmployeeSerials, 1)
|
||||
|
||||
var user models.User
|
||||
assert.NoError(t, database.DB.Where("employee_no = ?", "employee_no_pwd").First(&user).Error)
|
||||
assert.Empty(t, user.Password)
|
||||
|
||||
database.DB.Unscoped().Where("employee_id = ?", user.ID).Delete(&models.EmployeeSerial{})
|
||||
database.DB.Unscoped().Delete(&user)
|
||||
}
|
||||
|
||||
func TestUsersService_Create_BackendRoleRequiresPassword(t *testing.T) {
|
||||
svc := UsersService{}
|
||||
_, err := svc.Create(models.CreateUserDTO{
|
||||
Name: "无密码技术员",
|
||||
Phone: "13800000003",
|
||||
EmployeeNo: "tech_no_pwd",
|
||||
Position: "技术员",
|
||||
Role: "technician",
|
||||
})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "必须设置")
|
||||
}
|
||||
|
||||
func TestUsersService_Create_DuplicateUsername(t *testing.T) {
|
||||
@@ -39,10 +80,13 @@ func TestUsersService_Create_DuplicateUsername(t *testing.T) {
|
||||
|
||||
svc := UsersService{}
|
||||
_, err := svc.Create(models.CreateUserDTO{
|
||||
Username: "users_create_dup",
|
||||
Password: "password123",
|
||||
Name: "duplicate",
|
||||
Role: "technician",
|
||||
Username: "users_create_dup",
|
||||
Password: "password123",
|
||||
Name: "duplicate",
|
||||
Phone: "13800000004",
|
||||
EmployeeNo: "users_create_dup_2",
|
||||
Position: "技术员",
|
||||
Role: "technician",
|
||||
})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "用户名已存在")
|
||||
@@ -164,7 +208,7 @@ func TestUsersService_Delete_BlocksLastAdmin(t *testing.T) {
|
||||
func TestUsersService_FindAssignable_ReturnsAdminAndTechnician(t *testing.T) {
|
||||
a := models.User{Username: "assignable_admin", Password: "x", Name: "A", Role: "admin"}
|
||||
tech := models.User{Username: "assignable_tech", Password: "x", Name: "T", Role: "technician"}
|
||||
plain := models.User{Username: "assignable_user", Password: "x", Name: "U", Role: "user"}
|
||||
plain := models.User{Username: "assignable_user", Password: "x", Name: "U", Role: "employee"}
|
||||
database.DB.Create(&a)
|
||||
database.DB.Create(&tech)
|
||||
database.DB.Create(&plain)
|
||||
@@ -189,7 +233,7 @@ func TestUsersService_FindAssignable_ReturnsAdminAndTechnician(t *testing.T) {
|
||||
func TestUsersService_FindAll_FilterByRole(t *testing.T) {
|
||||
tech1 := models.User{Username: "findall_tech1", Password: "x", Name: "T1", Role: "technician"}
|
||||
tech2 := models.User{Username: "findall_tech2", Password: "x", Name: "T2", Role: "technician"}
|
||||
user1 := models.User{Username: "findall_user1", Password: "x", Name: "U1", Role: "user"}
|
||||
user1 := models.User{Username: "findall_user1", Password: "x", Name: "U1", Role: "employee"}
|
||||
database.DB.Create(&tech1)
|
||||
database.DB.Create(&tech2)
|
||||
database.DB.Create(&user1)
|
||||
|
||||
Reference in New Issue
Block a user