feat: restrict permission roles
This commit is contained in:
@@ -334,7 +334,7 @@ func (s *AftersalesService) Update(
|
||||
return &order, nil
|
||||
}
|
||||
|
||||
// SubmitForConfirmation 技术员提交客户确认
|
||||
// SubmitForConfirmation 工单负责人提交客户确认
|
||||
func (s *AftersalesService) SubmitForConfirmation(
|
||||
serialNumber string,
|
||||
dto models.SubmitForConfirmationDTO,
|
||||
@@ -539,7 +539,7 @@ func (s *AftersalesService) CustomerConfirm(serialNumber string, dto models.Cust
|
||||
return s.PublicQuery(normalized)
|
||||
}
|
||||
|
||||
// Reassign 重新分配技术员(仅管理员)
|
||||
// Reassign 重新分配工单负责人(仅管理员)
|
||||
func (s *AftersalesService) Reassign(serialNumber string, technicianID uint) (*models.AftersalesOrder, error) {
|
||||
var order models.AftersalesOrder
|
||||
result := database.DB.Where("serial_number = ?", normalizeAftersalesSerial(serialNumber)).First(&order)
|
||||
@@ -553,15 +553,15 @@ func (s *AftersalesService) Reassign(serialNumber string, technicianID uint) (*m
|
||||
|
||||
var technician models.User
|
||||
if err := database.DB.First(&technician, technicianID).Error; err != nil {
|
||||
return nil, errors.New("指定的技术员不存在")
|
||||
return nil, errors.New("指定的工单负责人不存在")
|
||||
}
|
||||
if technician.Role != "admin" && technician.Role != "technician" {
|
||||
return nil, errors.New("指定的用户不是技术员或管理员")
|
||||
if !models.IsAssignableWorkOrderRole(technician.Role) {
|
||||
return nil, errors.New("指定的用户不是可派单人员")
|
||||
}
|
||||
|
||||
order.TechnicianID = &technicianID
|
||||
if err := database.DB.Save(&order).Error; err != nil {
|
||||
return nil, fmt.Errorf("重新分配技术员失败: %w", err)
|
||||
return nil, fmt.Errorf("重新分配工单负责人失败: %w", err)
|
||||
}
|
||||
|
||||
_ = database.DB.Preload("Technician").Preload("Creator").Where("serial_number = ?", order.SerialNumber).First(&order)
|
||||
|
||||
@@ -23,7 +23,7 @@ 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" {
|
||||
if !models.HasBackendAccess(user.Role) {
|
||||
return nil, errors.New("用户名或密码不正确")
|
||||
}
|
||||
if user.Password == "" {
|
||||
|
||||
@@ -213,7 +213,7 @@ func (s *ProjectOrdersService) Update(
|
||||
return &order, nil
|
||||
}
|
||||
|
||||
// SubmitCompletion 技术员提交完成资料,进入待完成确认状态
|
||||
// SubmitCompletion 工单负责人提交完成资料,进入待完成确认状态
|
||||
func (s *ProjectOrdersService) SubmitCompletion(
|
||||
serialNumber string,
|
||||
dto models.SubmitProjectCompletionDTO,
|
||||
@@ -395,7 +395,7 @@ func (s *ProjectOrdersService) EngineerComplete(serialNumber string, dto models.
|
||||
return s.PublicQuery(normalized)
|
||||
}
|
||||
|
||||
// Reassign 重新分配技术员(仅管理员)
|
||||
// Reassign 重新分配工单负责人(仅管理员)
|
||||
func (s *ProjectOrdersService) Reassign(serialNumber string, technicianID uint) (*models.ProjectOrder, error) {
|
||||
var order models.ProjectOrder
|
||||
result := database.DB.Where("serial_number = ?", normalizeProjectOrderSerial(serialNumber)).First(&order)
|
||||
@@ -409,15 +409,15 @@ func (s *ProjectOrdersService) Reassign(serialNumber string, technicianID uint)
|
||||
|
||||
var technician models.User
|
||||
if err := database.DB.First(&technician, technicianID).Error; err != nil {
|
||||
return nil, errors.New("指定的技术员不存在")
|
||||
return nil, errors.New("指定的工单负责人不存在")
|
||||
}
|
||||
if technician.Role != "admin" && technician.Role != "technician" {
|
||||
return nil, errors.New("指定的用户不是技术员或管理员")
|
||||
if !models.IsAssignableWorkOrderRole(technician.Role) {
|
||||
return nil, errors.New("指定的用户不是可派单人员")
|
||||
}
|
||||
|
||||
order.TechnicianID = &technicianID
|
||||
if err := database.DB.Save(&order).Error; err != nil {
|
||||
return nil, fmt.Errorf("重新分配技术员失败: %w", err)
|
||||
return nil, fmt.Errorf("重新分配工单负责人失败: %w", err)
|
||||
}
|
||||
|
||||
_ = database.DB.Preload("Technician").Preload("Creator").Where("serial_number = ?", order.SerialNumber).First(&order)
|
||||
|
||||
+20
-12
@@ -31,11 +31,11 @@ func toUserDTO(user models.User) models.UserDTO {
|
||||
}
|
||||
|
||||
func hasBackendAccess(role string) bool {
|
||||
return role == "admin" || role == "technician"
|
||||
return models.HasBackendAccess(role)
|
||||
}
|
||||
|
||||
func isValidEmployeeRole(role string) bool {
|
||||
return role == "admin" || role == "technician" || role == "employee"
|
||||
func isValidManagedRole(role string) bool {
|
||||
return models.IsWorkOrderRole(role)
|
||||
}
|
||||
|
||||
// Create 创建用户
|
||||
@@ -62,11 +62,11 @@ func (s *UsersService) Create(dto models.CreateUserDTO) (*models.UserDTO, error)
|
||||
if position == "" {
|
||||
return nil, errors.New("岗位不能为空")
|
||||
}
|
||||
if !isValidEmployeeRole(role) {
|
||||
return nil, errors.New("角色不正确")
|
||||
if !isValidManagedRole(role) {
|
||||
return nil, errors.New("权限管理只能创建软件工程师、硬件工程师、商务经理、项目经理")
|
||||
}
|
||||
if hasBackendAccess(role) && len(dto.Password) < 6 {
|
||||
return nil, errors.New("管理员和技术员必须设置至少 6 位初始密码")
|
||||
return nil, errors.New("工单处理账号必须设置至少 6 位初始密码")
|
||||
}
|
||||
|
||||
var existing models.User
|
||||
@@ -133,7 +133,12 @@ func (s *UsersService) FindAll(page int, limit int, role string, search string)
|
||||
|
||||
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 + "%"
|
||||
@@ -159,11 +164,11 @@ func (s *UsersService) FindAll(page int, limit int, role string, search string)
|
||||
return result, int(total), totalPages, nil
|
||||
}
|
||||
|
||||
// FindAssignable 获取可分配的用户(admin + technician),用于售后工单分配
|
||||
// FindAssignable 获取可分配的工单处理人员,用于工单分配
|
||||
func (s *UsersService) FindAssignable() ([]models.UserDTO, error) {
|
||||
var users []models.User
|
||||
if err := database.DB.Where("role IN ?", []string{"admin", "technician"}).
|
||||
Order("role DESC, created_at ASC").Find(&users).Error; err != nil {
|
||||
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))
|
||||
@@ -206,8 +211,11 @@ func (s *UsersService) Update(userId uint, dto models.UpdateUserDTO, currentUser
|
||||
user.Position = strings.TrimSpace(dto.Position)
|
||||
}
|
||||
if dto.Role != "" {
|
||||
if !isValidEmployeeRole(dto.Role) {
|
||||
return nil, errors.New("角色不正确")
|
||||
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" {
|
||||
@@ -231,7 +239,7 @@ func (s *UsersService) ResetPassword(userId uint, newPassword string) error {
|
||||
return errors.New("用户不存在")
|
||||
}
|
||||
if !hasBackendAccess(user.Role) {
|
||||
return errors.New("员工无后台登录权限,不能设置密码")
|
||||
return errors.New("该账号无后台登录权限,不能设置密码")
|
||||
}
|
||||
|
||||
hashed, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
|
||||
|
||||
@@ -18,13 +18,13 @@ func TestUsersService_Create_Success(t *testing.T) {
|
||||
Email: "new@example.com",
|
||||
Phone: "13800000001",
|
||||
EmployeeNo: "users_create_ok",
|
||||
Position: "技术员",
|
||||
Role: "technician",
|
||||
Position: "软件工程师",
|
||||
Role: "software_engineer",
|
||||
})
|
||||
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, "software_engineer", dto.Role)
|
||||
assert.Equal(t, "13800000001", dto.Phone)
|
||||
assert.Equal(t, "users_create_ok", dto.EmployeeNo)
|
||||
assert.Len(t, dto.EmployeeSerials, 1)
|
||||
@@ -33,35 +33,17 @@ func TestUsersService_Create_Success(t *testing.T) {
|
||||
database.DB.Unscoped().Where("employee_name = ?", "新技术员").Delete(&models.EmployeeSerial{})
|
||||
}
|
||||
|
||||
func TestUsersService_Create_EmployeeWithoutPasswordGeneratesSerial(t *testing.T) {
|
||||
func TestUsersService_Create_BlocksEmployeeRole(t *testing.T) {
|
||||
svc := UsersService{}
|
||||
dto, err := svc.Create(models.CreateUserDTO{
|
||||
_, 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)
|
||||
|
||||
serialService := EmployeeSerialsService{}
|
||||
serial, err := serialService.Query(dto.EmployeeSerials[0].SerialNumber)
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, serial.Employee)
|
||||
assert.Equal(t, "普通员工", serial.Employee.Name)
|
||||
assert.Equal(t, "13800000002", serial.Employee.Phone)
|
||||
assert.Equal(t, "employee_no_pwd", serial.Employee.EmployeeNo)
|
||||
assert.Equal(t, "生产员工", serial.Employee.Position)
|
||||
|
||||
database.DB.Unscoped().Where("employee_id = ?", user.ID).Delete(&models.EmployeeSerial{})
|
||||
database.DB.Unscoped().Delete(&user)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "权限管理只能创建")
|
||||
}
|
||||
|
||||
func TestUsersService_Create_BackendRoleRequiresPassword(t *testing.T) {
|
||||
@@ -70,8 +52,8 @@ func TestUsersService_Create_BackendRoleRequiresPassword(t *testing.T) {
|
||||
Name: "无密码技术员",
|
||||
Phone: "13800000003",
|
||||
EmployeeNo: "tech_no_pwd",
|
||||
Position: "技术员",
|
||||
Role: "technician",
|
||||
Position: "软件工程师",
|
||||
Role: "software_engineer",
|
||||
})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "必须设置")
|
||||
@@ -82,7 +64,7 @@ func TestUsersService_Create_DuplicateUsername(t *testing.T) {
|
||||
Username: "users_create_dup",
|
||||
Password: "x",
|
||||
Name: "existing",
|
||||
Role: "technician",
|
||||
Role: "software_engineer",
|
||||
}
|
||||
database.DB.Create(&user)
|
||||
defer database.DB.Unscoped().Delete(&user)
|
||||
@@ -94,8 +76,8 @@ func TestUsersService_Create_DuplicateUsername(t *testing.T) {
|
||||
Name: "duplicate",
|
||||
Phone: "13800000004",
|
||||
EmployeeNo: "users_create_dup_2",
|
||||
Position: "技术员",
|
||||
Role: "technician",
|
||||
Position: "软件工程师",
|
||||
Role: "software_engineer",
|
||||
})
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "用户名已存在")
|
||||
@@ -113,10 +95,10 @@ func TestUsersService_Update_BlocksSelfDemotion(t *testing.T) {
|
||||
|
||||
svc := UsersService{}
|
||||
_, err := svc.Update(admin.ID, models.UpdateUserDTO{
|
||||
Role: "technician",
|
||||
Role: "software_engineer",
|
||||
}, admin.ID)
|
||||
assert.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "不能修改自己的管理员角色")
|
||||
assert.Contains(t, err.Error(), "不能通过权限管理修改管理员角色")
|
||||
}
|
||||
|
||||
func TestUsersService_Update_AllowsRoleChangeForOthers(t *testing.T) {
|
||||
@@ -130,7 +112,7 @@ func TestUsersService_Update_AllowsRoleChangeForOthers(t *testing.T) {
|
||||
Username: "users_update_target",
|
||||
Password: "x",
|
||||
Name: "target",
|
||||
Role: "technician",
|
||||
Role: "software_engineer",
|
||||
}
|
||||
database.DB.Create(¤tAdmin)
|
||||
database.DB.Create(&target)
|
||||
@@ -140,11 +122,11 @@ func TestUsersService_Update_AllowsRoleChangeForOthers(t *testing.T) {
|
||||
svc := UsersService{}
|
||||
updated, err := svc.Update(target.ID, models.UpdateUserDTO{
|
||||
Name: "新名字",
|
||||
Role: "admin",
|
||||
Role: "project_manager",
|
||||
}, currentAdmin.ID)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, "新名字", updated.Name)
|
||||
assert.Equal(t, "admin", updated.Role)
|
||||
assert.Equal(t, "project_manager", updated.Role)
|
||||
}
|
||||
|
||||
func TestUsersService_ResetPassword_ChangesHash(t *testing.T) {
|
||||
@@ -153,7 +135,7 @@ func TestUsersService_ResetPassword_ChangesHash(t *testing.T) {
|
||||
Username: "users_reset_pwd",
|
||||
Password: string(hashed),
|
||||
Name: "reset",
|
||||
Role: "technician",
|
||||
Role: "software_engineer",
|
||||
}
|
||||
database.DB.Create(&user)
|
||||
defer database.DB.Unscoped().Delete(&user)
|
||||
@@ -214,15 +196,18 @@ func TestUsersService_Delete_BlocksLastAdmin(t *testing.T) {
|
||||
database.DB.Unscoped().Delete(&last)
|
||||
}
|
||||
|
||||
func TestUsersService_FindAssignable_ReturnsAdminAndTechnician(t *testing.T) {
|
||||
func TestUsersService_FindAssignable_ReturnsWorkOrderRoles(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"}
|
||||
software := models.User{Username: "assignable_software", Password: "x", Name: "S", Role: "software_engineer"}
|
||||
plain := models.User{Username: "assignable_user", Password: "x", Name: "U", Role: "employee"}
|
||||
database.DB.Create(&a)
|
||||
database.DB.Create(&tech)
|
||||
database.DB.Create(&software)
|
||||
database.DB.Create(&plain)
|
||||
defer database.DB.Unscoped().Delete(&a)
|
||||
defer database.DB.Unscoped().Delete(&tech)
|
||||
defer database.DB.Unscoped().Delete(&software)
|
||||
defer database.DB.Unscoped().Delete(&plain)
|
||||
|
||||
svc := UsersService{}
|
||||
@@ -233,15 +218,17 @@ func TestUsersService_FindAssignable_ReturnsAdminAndTechnician(t *testing.T) {
|
||||
for _, u := range users {
|
||||
usernames[u.Username] = u.Role
|
||||
}
|
||||
assert.Equal(t, "admin", usernames["assignable_admin"])
|
||||
assert.Equal(t, "technician", usernames["assignable_tech"])
|
||||
assert.Equal(t, "software_engineer", usernames["assignable_software"])
|
||||
_, hasAdmin := usernames["assignable_admin"]
|
||||
assert.False(t, hasAdmin, "admin should not be assignable")
|
||||
_, hasPlain := usernames["assignable_user"]
|
||||
assert.False(t, hasPlain, "plain user should not be assignable")
|
||||
}
|
||||
|
||||
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"}
|
||||
tech1 := models.User{Username: "findall_tech1", Password: "x", Name: "T1", Role: "software_engineer"}
|
||||
tech2 := models.User{Username: "findall_tech2", Password: "x", Name: "T2", Role: "software_engineer"}
|
||||
user1 := models.User{Username: "findall_user1", Password: "x", Name: "U1", Role: "employee"}
|
||||
database.DB.Create(&tech1)
|
||||
database.DB.Create(&tech2)
|
||||
@@ -251,9 +238,9 @@ func TestUsersService_FindAll_FilterByRole(t *testing.T) {
|
||||
defer database.DB.Unscoped().Delete(&user1)
|
||||
|
||||
svc := UsersService{}
|
||||
results, _, _, err := svc.FindAll(1, 50, "technician", "")
|
||||
results, _, _, err := svc.FindAll(1, 50, "software_engineer", "")
|
||||
assert.NoError(t, err)
|
||||
for _, u := range results {
|
||||
assert.Equal(t, "technician", u.Role)
|
||||
assert.Equal(t, "software_engineer", u.Role)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user