Refactor employee management
This commit is contained in:
@@ -50,7 +50,7 @@ backend-go/
|
|||||||
│ ├── employees_controller.go # Employee serials: generate, query, update, revoke, qrcode
|
│ ├── employees_controller.go # Employee serials: generate, query, update, revoke, qrcode
|
||||||
│ ├── helper.go # Helper functions (GetCurrentUser, BindJSON, Response)
|
│ ├── helper.go # Helper functions (GetCurrentUser, BindJSON, Response)
|
||||||
│ ├── serials_controller.go # Company serials: generate, query, update, revoke, qrcode
|
│ ├── serials_controller.go # Company serials: generate, query, update, revoke, qrcode
|
||||||
│ └── users_controller.go # User management (admin): create, list, update, reset password, delete
|
│ └── users_controller.go # Employee master data / backend account management
|
||||||
├── database/ # Database connection and migrations
|
├── database/ # Database connection and migrations
|
||||||
│ └── database.go # GORM init, AutoMigrate
|
│ └── database.go # GORM init, AutoMigrate
|
||||||
├── docs/ # Swagger documentation (auto-generated)
|
├── docs/ # Swagger documentation (auto-generated)
|
||||||
@@ -70,8 +70,8 @@ backend-go/
|
|||||||
│ ├── serials_service.go # Company serials: generate, query, update, revoke, qrcode
|
│ ├── serials_service.go # Company serials: generate, query, update, revoke, qrcode
|
||||||
│ ├── aftersales_service_test.go # Aftersales unit tests
|
│ ├── aftersales_service_test.go # Aftersales unit tests
|
||||||
│ ├── services_test.go # Auth / Serials / Employees / Companies unit tests
|
│ ├── services_test.go # Auth / Serials / Employees / Companies unit tests
|
||||||
│ ├── users_service.go # User CRUD, role management, password reset (admin)
|
│ ├── users_service.go # Employee CRUD, role management, password reset (admin)
|
||||||
│ └── users_service_test.go # Users unit tests
|
│ └── users_service_test.go # Employee/user account unit tests
|
||||||
├── tests/ # Integration tests
|
├── tests/ # Integration tests
|
||||||
│ └── main_test.go # End-to-end tests
|
│ └── main_test.go # End-to-end tests
|
||||||
├── data/ # SQLite data directory
|
├── data/ # SQLite data directory
|
||||||
@@ -97,7 +97,15 @@ backend-go/
|
|||||||
- **Aftersales** (技术员+管理员): `POST /api/aftersales`, `GET /api/aftersales`, `GET /api/aftersales/:serialNumber`, `PATCH /api/aftersales/:serialNumber`, `POST /api/aftersales/:serialNumber/qrcode`, `POST /api/aftersales/:serialNumber/submit`
|
- **Aftersales** (技术员+管理员): `POST /api/aftersales`, `GET /api/aftersales`, `GET /api/aftersales/:serialNumber`, `PATCH /api/aftersales/:serialNumber`, `POST /api/aftersales/:serialNumber/qrcode`, `POST /api/aftersales/:serialNumber/submit`
|
||||||
- **Aftersales** (仅管理员): `POST /api/aftersales/:serialNumber/reassign`, `POST /api/aftersales/:serialNumber/force-close`, `DELETE /api/aftersales/:serialNumber`
|
- **Aftersales** (仅管理员): `POST /api/aftersales/:serialNumber/reassign`, `POST /api/aftersales/:serialNumber/force-close`, `DELETE /api/aftersales/:serialNumber`
|
||||||
- **Users** (技术员+管理员): `GET /api/users/assignable`
|
- **Users** (技术员+管理员): `GET /api/users/assignable`
|
||||||
- **Users** (仅管理员): `POST /api/users`, `GET /api/users`, `PATCH /api/users/:id`, `POST /api/users/:id/reset-password`, `DELETE /api/users/:id`
|
- **Employees** (仅管理员): `POST /api/employees`, `GET /api/employees`, `PATCH /api/employees/:id`, `POST /api/employees/:id/reset-password`, `DELETE /api/employees/:id`
|
||||||
|
|
||||||
|
### Roles and permissions
|
||||||
|
- Roles are limited to `admin`, `technician`, and `employee`.
|
||||||
|
- `admin` has full backend access.
|
||||||
|
- `technician` only has aftersales/work-order module access.
|
||||||
|
- `employee` has no backend login access and does not require a password.
|
||||||
|
- Creating an employee through `/api/employees` creates employee master data and automatically generates one employee serial bound by `employeeId`.
|
||||||
|
- `admin` / `technician` creation requires an initial password; `employee` creation must not require one.
|
||||||
|
|
||||||
### Import Organization
|
### Import Organization
|
||||||
Standard imports followed by third-party imports, then project imports (sorted alphabetically):
|
Standard imports followed by third-party imports, then project imports (sorted alphabetically):
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ deps:
|
|||||||
# 生成 Swagger 文档
|
# 生成 Swagger 文档
|
||||||
swagger:
|
swagger:
|
||||||
@echo "生成 Swagger 文档..."
|
@echo "生成 Swagger 文档..."
|
||||||
@swag init -g main.go
|
@go run github.com/swaggo/swag/cmd/swag@v1.16.6 init -g main.go
|
||||||
@echo "Swagger 文档已生成在 docs/ 目录"
|
@echo "Swagger 文档已生成在 docs/ 目录"
|
||||||
|
|
||||||
# 初始化数据库
|
# 初始化数据库
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ backend-go/
|
|||||||
│ ├── employees_controller.go # 员工赋码接口
|
│ ├── employees_controller.go # 员工赋码接口
|
||||||
│ ├── helper.go # 控制器通用辅助函数
|
│ ├── helper.go # 控制器通用辅助函数
|
||||||
│ ├── serials_controller.go # 序列号管理接口
|
│ ├── serials_controller.go # 序列号管理接口
|
||||||
│ └── users_controller.go # 用户管理接口(仅管理员)
|
│ └── users_controller.go # 员工主档管理接口(仅管理员)
|
||||||
├── database/ # 数据库连接和操作
|
├── database/ # 数据库连接和操作
|
||||||
│ └── database.go # 数据库初始化、连接池配置
|
│ └── database.go # 数据库初始化、连接池配置
|
||||||
├── docs/ # Swagger API 文档(自动生成)
|
├── docs/ # Swagger API 文档(自动生成)
|
||||||
@@ -58,8 +58,8 @@ backend-go/
|
|||||||
│ ├── serials_service.go # 序列号业务逻辑
|
│ ├── serials_service.go # 序列号业务逻辑
|
||||||
│ ├── aftersales_service_test.go # 售后工单单元测试
|
│ ├── aftersales_service_test.go # 售后工单单元测试
|
||||||
│ ├── services_test.go # 认证/序列号/员工/企业单元测试
|
│ ├── services_test.go # 认证/序列号/员工/企业单元测试
|
||||||
│ ├── users_service.go # 用户管理业务逻辑
|
│ ├── users_service.go # 员工主档/后台账号业务逻辑
|
||||||
│ └── users_service_test.go # 用户管理单元测试
|
│ └── users_service_test.go # 员工主档/后台账号单元测试
|
||||||
├── tests/ # 集成测试
|
├── tests/ # 集成测试
|
||||||
│ └── main_test.go # 端到端测试
|
│ └── main_test.go # 端到端测试
|
||||||
├── data/ # 数据目录(SQLite 数据库存储位置)
|
├── data/ # 数据目录(SQLite 数据库存储位置)
|
||||||
@@ -197,7 +197,7 @@ curl -X GET http://localhost:3000/api/health
|
|||||||
```bash
|
```bash
|
||||||
curl -X POST http://localhost:3000/api/auth/login \
|
curl -X POST http://localhost:3000/api/auth/login \
|
||||||
-H "Content-Type: application/json" \
|
-H "Content-Type: application/json" \
|
||||||
-d '{"username":"admin","password":"password123"}'
|
-d '{"username":"admin","password":"Beifan@2026"}'
|
||||||
```
|
```
|
||||||
|
|
||||||
首次启动且用户表为空时,系统会自动创建默认管理员账号(请在生产环境立即修改密码):
|
首次启动且用户表为空时,系统会自动创建默认管理员账号(请在生产环境立即修改密码):
|
||||||
@@ -227,11 +227,11 @@ http://localhost:3000/swagger/index.html
|
|||||||
如果修改了代码中的 API 注解,需要重新生成 Swagger 文档:
|
如果修改了代码中的 API 注解,需要重新生成 Swagger 文档:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 确保已安装 swag 工具
|
|
||||||
go install github.com/swaggo/swag/cmd/swag@latest
|
|
||||||
|
|
||||||
# 生成文档
|
# 生成文档
|
||||||
swag init -g main.go
|
make swagger
|
||||||
|
|
||||||
|
# 或直接执行
|
||||||
|
go run github.com/swaggo/swag/cmd/swag@v1.16.6 init -g main.go
|
||||||
|
|
||||||
# 文档将生成在 docs/ 目录下
|
# 文档将生成在 docs/ 目录下
|
||||||
```
|
```
|
||||||
@@ -254,9 +254,9 @@ swag init -g main.go
|
|||||||
| ---- | ----------------------------------- | ---------------- | -------- | ------ |
|
| ---- | ----------------------------------- | ---------------- | -------- | ------ |
|
||||||
| POST | `/api/serials/generate` | 生成序列号 | 是 | 管理员 |
|
| POST | `/api/serials/generate` | 生成序列号 | 是 | 管理员 |
|
||||||
| POST | `/api/serials/generate-with-prefix` | 带前缀生成序列号 | 是 | 管理员 |
|
| POST | `/api/serials/generate-with-prefix` | 带前缀生成序列号 | 是 | 管理员 |
|
||||||
| POST | `/api/serials/:serialNumber/qrcode` | 生成序列号二维码 | 是 | 任何 |
|
| POST | `/api/serials/:serialNumber/qrcode` | 生成序列号二维码 | 是 | 管理员 |
|
||||||
| GET | `/api/serials/:serialNumber/query` | 查询序列号信息 | 否 | 任何 |
|
| GET | `/api/serials/:serialNumber/query` | 查询序列号信息 | 否 | 任何 |
|
||||||
| GET | `/api/serials` | 获取序列号列表 | 是 | 任何 |
|
| GET | `/api/serials` | 获取序列号列表 | 是 | 管理员 |
|
||||||
| PATCH | `/api/serials/:serialNumber` | 更新序列号信息 | 是 | 管理员 |
|
| PATCH | `/api/serials/:serialNumber` | 更新序列号信息 | 是 | 管理员 |
|
||||||
| PUT | `/api/serials/:serialNumber` | 更新序列号信息 | 是 | 管理员 |
|
| PUT | `/api/serials/:serialNumber` | 更新序列号信息 | 是 | 管理员 |
|
||||||
| POST | `/api/serials/:serialNumber/revoke` | 吊销序列号 | 是 | 管理员 |
|
| POST | `/api/serials/:serialNumber/revoke` | 吊销序列号 | 是 | 管理员 |
|
||||||
@@ -280,9 +280,9 @@ swag init -g main.go
|
|||||||
| 方法 | 路径 | 描述 | 需要认证 | 角色 |
|
| 方法 | 路径 | 描述 | 需要认证 | 角色 |
|
||||||
| ---- | -------------------------------------- | ------------------ | -------- | ------ |
|
| ---- | -------------------------------------- | ------------------ | -------- | ------ |
|
||||||
| POST | `/api/employee-serials/generate` | 生成员工序列号 | 是 | 管理员 |
|
| POST | `/api/employee-serials/generate` | 生成员工序列号 | 是 | 管理员 |
|
||||||
| GET | `/api/employee-serials` | 获取员工序列号列表 | 是 | 任何 |
|
| GET | `/api/employee-serials` | 获取员工序列号列表 | 是 | 管理员 |
|
||||||
| GET | `/api/employee-serials/:serialNumber/query` | 查询员工序列号信息 | 否 | 任何 |
|
| GET | `/api/employee-serials/:serialNumber/query` | 查询员工序列号信息 | 否 | 任何 |
|
||||||
| POST | `/api/employee-serials/:serialNumber/qrcode` | 生成员工二维码 | 是 | 任何 |
|
| POST | `/api/employee-serials/:serialNumber/qrcode` | 生成员工二维码 | 是 | 管理员 |
|
||||||
| PATCH | `/api/employee-serials/:serialNumber` | 更新员工序列号信息 | 是 | 管理员 |
|
| PATCH | `/api/employee-serials/:serialNumber` | 更新员工序列号信息 | 是 | 管理员 |
|
||||||
| PUT | `/api/employee-serials/:serialNumber` | 更新员工序列号信息 | 是 | 管理员 |
|
| PUT | `/api/employee-serials/:serialNumber` | 更新员工序列号信息 | 是 | 管理员 |
|
||||||
| POST | `/api/employee-serials/:serialNumber/revoke` | 吊销员工序列号 | 是 | 管理员 |
|
| POST | `/api/employee-serials/:serialNumber/revoke` | 吊销员工序列号 | 是 | 管理员 |
|
||||||
@@ -295,7 +295,8 @@ swag init -g main.go
|
|||||||
|
|
||||||
**员工序列号特点**:
|
**员工序列号特点**:
|
||||||
- 无有效期限制(与企业赋码不同)
|
- 无有效期限制(与企业赋码不同)
|
||||||
- 包含部门(department)和员工姓名(employeeName)信息
|
- 创建员工主档时会自动生成 1 个员工码,并通过 `employeeId` 绑定员工
|
||||||
|
- 包含岗位(position)和员工姓名(employeeName)信息
|
||||||
- 序列号格式: `EMP26xxxxxx`(EMP + 年份后两位 + 6位随机字符)
|
- 序列号格式: `EMP26xxxxxx`(EMP + 年份后两位 + 6位随机字符)
|
||||||
|
|
||||||
### 售后工单
|
### 售后工单
|
||||||
@@ -325,25 +326,26 @@ swag init -g main.go
|
|||||||
- 工单状态机: `created` → `pending_confirmation` → `closed` / `rejected`,被退回后可重新提交
|
- 工单状态机: `created` → `pending_confirmation` → `closed` / `rejected`,被退回后可重新提交
|
||||||
- 公开查询不返回手机号(脱敏)
|
- 公开查询不返回手机号(脱敏)
|
||||||
|
|
||||||
### 用户管理(仅管理员)
|
### 员工管理(仅管理员)
|
||||||
|
|
||||||
| 方法 | 路径 | 描述 | 需要认证 | 角色 |
|
| 方法 | 路径 | 描述 | 需要认证 | 角色 |
|
||||||
| ------ | ----------------------------------- | -------------------------- | -------- | ------------- |
|
| ------ | ----------------------------------- | -------------------------- | -------- | ------------- |
|
||||||
| GET | `/api/users/assignable` | 可分配用户列表(用于售后) | 是 | 管理员/技术员 |
|
| GET | `/api/users/assignable` | 可分配用户列表(用于售后) | 是 | 管理员/技术员 |
|
||||||
| POST | `/api/users` | 创建用户 | 是 | 管理员 |
|
| POST | `/api/employees` | 创建员工并自动生成员工码 | 是 | 管理员 |
|
||||||
| GET | `/api/users` | 用户列表(分页+筛选) | 是 | 管理员 |
|
| GET | `/api/employees` | 员工列表(分页+筛选) | 是 | 管理员 |
|
||||||
| PATCH | `/api/users/:id` | 更新用户姓名/邮箱/角色 | 是 | 管理员 |
|
| PATCH | `/api/employees/:id` | 更新员工姓名/电话/工号/岗位/角色 | 是 | 管理员 |
|
||||||
| POST | `/api/users/:id/reset-password` | 重置用户密码 | 是 | 管理员 |
|
| POST | `/api/employees/:id/reset-password` | 重置后台账号密码 | 是 | 管理员 |
|
||||||
| DELETE | `/api/users/:id` | 删除用户 | 是 | 管理员 |
|
| DELETE | `/api/employees/:id` | 删除员工 | 是 | 管理员 |
|
||||||
|
|
||||||
**用户角色**:
|
**员工角色**:
|
||||||
- `admin`:完整权限,包括用户管理、强制关闭工单、工单分配(重新分配技术员)、删除工单
|
- `admin`:管理员,拥有全部后台权限,包括企业管理、员工管理、工单分配、强制关闭和删除工单
|
||||||
- `technician`:可创建/编辑自己负责的售后工单,可使用 `assignable` 查询同事
|
- `technician`:技术员,仅拥有工单模块权限,可创建/处理工单,可使用 `assignable` 查询可分配同事
|
||||||
- `user`:保留角色(暂未实际启用)
|
- `employee`:员工,无后台登录权限,不需要密码,仅用于员工主档和员工码查询
|
||||||
|
|
||||||
**保护规则**:
|
**保护规则**:
|
||||||
- 不能删除自己;不能将自己的 admin 角色降级;不能删除最后一个 admin
|
- 不能删除自己;不能将自己的 admin 角色降级;不能删除最后一个 admin
|
||||||
- 默认创建用户密码 bcrypt 加密存储
|
- 创建 `admin` / `technician` 必须设置初始密码,密码 bcrypt 加密存储
|
||||||
|
- 创建 `employee` 不要求密码,且不能登录后台
|
||||||
|
|
||||||
## 测试
|
## 测试
|
||||||
|
|
||||||
@@ -376,7 +378,7 @@ go tool cover -html=coverage.out
|
|||||||
- 员工赋码测试(生成、查询、更新、吊销、二维码生成)
|
- 员工赋码测试(生成、查询、更新、吊销、二维码生成)
|
||||||
- 企业统计测试(统计概览)
|
- 企业统计测试(统计概览)
|
||||||
- 售后工单测试(YYMMDDNN 序号生成、状态机、客户确认手机号校验、强制关闭)
|
- 售后工单测试(YYMMDDNN 序号生成、状态机、客户确认手机号校验、强制关闭)
|
||||||
- 用户管理测试(重复用户名、自降级保护、最后管理员保护、密码重置)
|
- 员工管理测试(创建员工自动生成员工码、重复工号、自降级保护、最后管理员保护、密码重置)
|
||||||
- **tests/**: 集成测试(健康检查、登录流程)
|
- **tests/**: 集成测试(健康检查、登录流程)
|
||||||
|
|
||||||
## 代码检查
|
## 代码检查
|
||||||
|
|||||||
@@ -58,12 +58,15 @@ func (c *AuthController) Login(ctx *gin.Context) {
|
|||||||
"message": "登录成功",
|
"message": "登录成功",
|
||||||
"accessToken": token,
|
"accessToken": token,
|
||||||
"user": models.UserDTO{
|
"user": models.UserDTO{
|
||||||
ID: user.ID,
|
ID: user.ID,
|
||||||
Username: user.Username,
|
Username: user.Username,
|
||||||
Name: user.Name,
|
Name: user.Name,
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
Role: user.Role,
|
Phone: user.Phone,
|
||||||
CreatedAt: user.CreatedAt,
|
EmployeeNo: user.EmployeeNo,
|
||||||
|
Position: user.Position,
|
||||||
|
Role: user.Role,
|
||||||
|
CreatedAt: user.CreatedAt,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"git.beifan.cn/trace-system/backend-go/services"
|
"git.beifan.cn/trace-system/backend-go/services"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UsersController 用户管理控制器
|
// UsersController 员工管理控制器
|
||||||
type UsersController struct {
|
type UsersController struct {
|
||||||
usersService services.UsersService
|
usersService services.UsersService
|
||||||
}
|
}
|
||||||
@@ -22,17 +22,17 @@ func NewUsersController() *UsersController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create 创建用户(管理员)
|
// Create 创建员工(管理员)
|
||||||
// @Summary 创建用户
|
// @Summary 创建员工
|
||||||
// @Tags 用户管理
|
// @Tags 员工管理
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth
|
||||||
// @Param data body models.CreateUserDTO true "用户数据"
|
// @Param data body models.CreateUserDTO true "员工数据"
|
||||||
// @Success 200 {object} models.DataResponse
|
// @Success 200 {object} models.DataResponse
|
||||||
// @Failure 400 {object} models.ErrorResponse
|
// @Failure 400 {object} models.ErrorResponse
|
||||||
// @Failure 401 {object} models.ErrorResponse
|
// @Failure 401 {object} models.ErrorResponse
|
||||||
// @Router /users [post]
|
// @Router /employees [post]
|
||||||
func (c *UsersController) Create(ctx *gin.Context) {
|
func (c *UsersController) Create(ctx *gin.Context) {
|
||||||
var dto models.CreateUserDTO
|
var dto models.CreateUserDTO
|
||||||
if !BindJSON(ctx, &dto) {
|
if !BindJSON(ctx, &dto) {
|
||||||
@@ -45,14 +45,14 @@ func (c *UsersController) Create(ctx *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
SuccessResponse(ctx, "用户创建成功", gin.H{
|
SuccessResponse(ctx, "员工创建成功", gin.H{
|
||||||
"user": user,
|
"employee": user,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// FindAll 用户列表
|
// FindAll 员工列表
|
||||||
// @Summary 用户列表
|
// @Summary 员工列表
|
||||||
// @Tags 用户管理
|
// @Tags 员工管理
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth
|
||||||
// @Param page query int false "页码"
|
// @Param page query int false "页码"
|
||||||
@@ -61,7 +61,7 @@ func (c *UsersController) Create(ctx *gin.Context) {
|
|||||||
// @Param search query string false "搜索"
|
// @Param search query string false "搜索"
|
||||||
// @Success 200 {object} models.PaginationResponse
|
// @Success 200 {object} models.PaginationResponse
|
||||||
// @Failure 401 {object} models.ErrorResponse
|
// @Failure 401 {object} models.ErrorResponse
|
||||||
// @Router /users [get]
|
// @Router /employees [get]
|
||||||
func (c *UsersController) FindAll(ctx *gin.Context) {
|
func (c *UsersController) FindAll(ctx *gin.Context) {
|
||||||
page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1"))
|
page, _ := strconv.Atoi(ctx.DefaultQuery("page", "1"))
|
||||||
limit, _ := strconv.Atoi(ctx.DefaultQuery("limit", "20"))
|
limit, _ := strconv.Atoi(ctx.DefaultQuery("limit", "20"))
|
||||||
@@ -74,7 +74,7 @@ func (c *UsersController) FindAll(ctx *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
SuccessResponse(ctx, "获取用户列表成功", gin.H{
|
SuccessResponse(ctx, "获取员工列表成功", gin.H{
|
||||||
"data": users,
|
"data": users,
|
||||||
"pagination": gin.H{
|
"pagination": gin.H{
|
||||||
"page": page,
|
"page": page,
|
||||||
@@ -106,9 +106,9 @@ func (c *UsersController) FindAssignable(ctx *gin.Context) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update 更新用户信息
|
// Update 更新员工信息
|
||||||
// @Summary 更新用户
|
// @Summary 更新员工
|
||||||
// @Tags 用户管理
|
// @Tags 员工管理
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth
|
||||||
@@ -117,7 +117,7 @@ func (c *UsersController) FindAssignable(ctx *gin.Context) {
|
|||||||
// @Success 200 {object} models.DataResponse
|
// @Success 200 {object} models.DataResponse
|
||||||
// @Failure 400 {object} models.ErrorResponse
|
// @Failure 400 {object} models.ErrorResponse
|
||||||
// @Failure 401 {object} models.ErrorResponse
|
// @Failure 401 {object} models.ErrorResponse
|
||||||
// @Router /users/{id} [patch]
|
// @Router /employees/{id} [patch]
|
||||||
func (c *UsersController) Update(ctx *gin.Context) {
|
func (c *UsersController) Update(ctx *gin.Context) {
|
||||||
userModel, ok := GetCurrentUser(ctx)
|
userModel, ok := GetCurrentUser(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -141,14 +141,14 @@ func (c *UsersController) Update(ctx *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
SuccessResponse(ctx, "用户更新成功", gin.H{
|
SuccessResponse(ctx, "员工更新成功", gin.H{
|
||||||
"user": user,
|
"employee": user,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResetPassword 重置用户密码
|
// ResetPassword 重置用户密码
|
||||||
// @Summary 重置密码
|
// @Summary 重置密码
|
||||||
// @Tags 用户管理
|
// @Tags 员工管理
|
||||||
// @Accept json
|
// @Accept json
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth
|
||||||
@@ -157,7 +157,7 @@ func (c *UsersController) Update(ctx *gin.Context) {
|
|||||||
// @Success 200 {object} models.BaseResponse
|
// @Success 200 {object} models.BaseResponse
|
||||||
// @Failure 400 {object} models.ErrorResponse
|
// @Failure 400 {object} models.ErrorResponse
|
||||||
// @Failure 401 {object} models.ErrorResponse
|
// @Failure 401 {object} models.ErrorResponse
|
||||||
// @Router /users/{id}/reset-password [post]
|
// @Router /employees/{id}/reset-password [post]
|
||||||
func (c *UsersController) ResetPassword(ctx *gin.Context) {
|
func (c *UsersController) ResetPassword(ctx *gin.Context) {
|
||||||
id, err := strconv.ParseUint(ctx.Param("id"), 10, 32)
|
id, err := strconv.ParseUint(ctx.Param("id"), 10, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -178,16 +178,16 @@ func (c *UsersController) ResetPassword(ctx *gin.Context) {
|
|||||||
SuccessResponse(ctx, "密码重置成功")
|
SuccessResponse(ctx, "密码重置成功")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete 删除用户
|
// Delete 删除员工
|
||||||
// @Summary 删除用户
|
// @Summary 删除员工
|
||||||
// @Tags 用户管理
|
// @Tags 员工管理
|
||||||
// @Produce json
|
// @Produce json
|
||||||
// @Security BearerAuth
|
// @Security BearerAuth
|
||||||
// @Param id path int true "用户 ID"
|
// @Param id path int true "用户 ID"
|
||||||
// @Success 200 {object} models.BaseResponse
|
// @Success 200 {object} models.BaseResponse
|
||||||
// @Failure 400 {object} models.ErrorResponse
|
// @Failure 400 {object} models.ErrorResponse
|
||||||
// @Failure 401 {object} models.ErrorResponse
|
// @Failure 401 {object} models.ErrorResponse
|
||||||
// @Router /users/{id} [delete]
|
// @Router /employees/{id} [delete]
|
||||||
func (c *UsersController) Delete(ctx *gin.Context) {
|
func (c *UsersController) Delete(ctx *gin.Context) {
|
||||||
userModel, ok := GetCurrentUser(ctx)
|
userModel, ok := GetCurrentUser(ctx)
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -205,5 +205,5 @@ func (c *UsersController) Delete(ctx *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
SuccessResponse(ctx, "用户删除成功")
|
SuccessResponse(ctx, "员工删除成功")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,11 +148,14 @@ func seedAdminUser() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
admin := models.User{
|
admin := models.User{
|
||||||
Username: adminUsername,
|
Username: adminUsername,
|
||||||
Password: string(hashedPassword),
|
Password: string(hashedPassword),
|
||||||
Name: adminName,
|
Name: adminName,
|
||||||
Email: adminEmail,
|
Email: adminEmail,
|
||||||
Role: "admin",
|
Phone: "00000000000",
|
||||||
|
EmployeeNo: adminUsername,
|
||||||
|
Position: "管理员",
|
||||||
|
Role: "admin",
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := DB.Create(&admin).Error; err != nil {
|
if err := DB.Create(&admin).Error; err != nil {
|
||||||
|
|||||||
+1880
-1
File diff suppressed because it is too large
Load Diff
+1880
-1
File diff suppressed because it is too large
Load Diff
+1209
-1
File diff suppressed because it is too large
Load Diff
+53
-37
@@ -8,16 +8,20 @@ import (
|
|||||||
|
|
||||||
// User 模型
|
// User 模型
|
||||||
type User struct {
|
type User struct {
|
||||||
ID uint `gorm:"primaryKey" json:"id"`
|
ID uint `gorm:"primaryKey" json:"id"`
|
||||||
Username string `gorm:"uniqueIndex;size:255" json:"username"`
|
Username string `gorm:"uniqueIndex;size:255" json:"username"`
|
||||||
Password string `gorm:"size:255" json:"-"`
|
Password string `gorm:"size:255" json:"-"`
|
||||||
Name string `gorm:"size:255" json:"name"`
|
Name string `gorm:"size:255" json:"name"`
|
||||||
Email string `gorm:"size:255" json:"email"`
|
Email string `gorm:"size:255" json:"email"`
|
||||||
Role string `gorm:"size:50;default:'user'" json:"role"`
|
Phone string `gorm:"size:50" json:"phone"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
EmployeeNo string `gorm:"uniqueIndex;size:100" json:"employeeNo"`
|
||||||
UpdatedAt time.Time `json:"updatedAt"`
|
Position string `gorm:"size:255" json:"position"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
Role string `gorm:"size:50;default:'employee'" json:"role"`
|
||||||
Serials []Serial `gorm:"foreignKey:CreatedBy" json:"-"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
|
Serials []Serial `gorm:"foreignKey:CreatedBy" json:"-"`
|
||||||
|
EmployeeSerials []EmployeeSerial `gorm:"foreignKey:EmployeeID" json:"employeeSerials,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Company 模型
|
// Company 模型
|
||||||
@@ -48,12 +52,16 @@ type Serial struct {
|
|||||||
|
|
||||||
// UserDTO 数据传输对象
|
// UserDTO 数据传输对象
|
||||||
type UserDTO struct {
|
type UserDTO struct {
|
||||||
ID uint `json:"id"`
|
ID uint `json:"id"`
|
||||||
Username string `json:"username"`
|
Username string `json:"username"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Email string `json:"email"`
|
Email string `json:"email"`
|
||||||
Role string `json:"role"`
|
Phone string `json:"phone"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
EmployeeNo string `json:"employeeNo"`
|
||||||
|
Position string `json:"position"`
|
||||||
|
Role string `json:"role"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
EmployeeSerials []EmployeeSerial `json:"employeeSerials,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoginDTO 登录请求数据
|
// LoginDTO 登录请求数据
|
||||||
@@ -76,18 +84,24 @@ type UpdateProfileDTO struct {
|
|||||||
|
|
||||||
// CreateUserDTO 管理员创建用户请求
|
// CreateUserDTO 管理员创建用户请求
|
||||||
type CreateUserDTO struct {
|
type CreateUserDTO struct {
|
||||||
Username string `json:"username" validate:"required,min=3,max=50"`
|
Username string `json:"username,omitempty"`
|
||||||
Password string `json:"password" validate:"required,min=6"`
|
Password string `json:"password,omitempty"`
|
||||||
Name string `json:"name" validate:"required"`
|
Name string `json:"name" validate:"required"`
|
||||||
Email string `json:"email" validate:"omitempty,email"`
|
Email string `json:"email" validate:"omitempty,email"`
|
||||||
Role string `json:"role" validate:"required,oneof=admin technician employee user"`
|
Phone string `json:"phone" validate:"required"`
|
||||||
|
EmployeeNo string `json:"employeeNo" validate:"required"`
|
||||||
|
Position string `json:"position" validate:"required"`
|
||||||
|
Role string `json:"role" validate:"required,oneof=admin technician employee"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateUserDTO 管理员更新用户信息请求
|
// UpdateUserDTO 管理员更新用户信息请求
|
||||||
type UpdateUserDTO struct {
|
type UpdateUserDTO struct {
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
Email string `json:"email,omitempty" validate:"omitempty,email"`
|
Email string `json:"email,omitempty" validate:"omitempty,email"`
|
||||||
Role string `json:"role,omitempty" validate:"omitempty,oneof=admin technician employee user"`
|
Phone string `json:"phone,omitempty"`
|
||||||
|
EmployeeNo string `json:"employeeNo,omitempty"`
|
||||||
|
Position string `json:"position,omitempty"`
|
||||||
|
Role string `json:"role,omitempty" validate:"omitempty,oneof=admin technician employee"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// AdminResetPasswordDTO 管理员重置用户密码
|
// AdminResetPasswordDTO 管理员重置用户密码
|
||||||
@@ -187,19 +201,19 @@ type CompanyUpdateRequest struct {
|
|||||||
|
|
||||||
// CompanyStatsOverviewDTO 企业统计概览
|
// CompanyStatsOverviewDTO 企业统计概览
|
||||||
type CompanyStatsOverviewDTO struct {
|
type CompanyStatsOverviewDTO struct {
|
||||||
TotalCompanies int64 `json:"totalCompanies"`
|
TotalCompanies int64 `json:"totalCompanies"`
|
||||||
ActiveCompanies int64 `json:"activeCompanies"`
|
ActiveCompanies int64 `json:"activeCompanies"`
|
||||||
InactiveCompanies int64 `json:"inactiveCompanies"`
|
InactiveCompanies int64 `json:"inactiveCompanies"`
|
||||||
TotalSerials int64 `json:"totalSerials"`
|
TotalSerials int64 `json:"totalSerials"`
|
||||||
ActiveSerials int64 `json:"activeSerials"`
|
ActiveSerials int64 `json:"activeSerials"`
|
||||||
RevokedSerials int64 `json:"revokedSerials"`
|
RevokedSerials int64 `json:"revokedSerials"`
|
||||||
TotalEmployeeSerials int64 `json:"totalEmployeeSerials"`
|
TotalEmployeeSerials int64 `json:"totalEmployeeSerials"`
|
||||||
ActiveEmployeeSerials int64 `json:"activeEmployeeSerials"`
|
ActiveEmployeeSerials int64 `json:"activeEmployeeSerials"`
|
||||||
RevokedEmployeeSerials int64 `json:"revokedEmployeeSerials"`
|
RevokedEmployeeSerials int64 `json:"revokedEmployeeSerials"`
|
||||||
TotalAftersales int64 `json:"totalAftersales"`
|
TotalAftersales int64 `json:"totalAftersales"`
|
||||||
PendingConfirmation int64 `json:"pendingConfirmation"`
|
PendingConfirmation int64 `json:"pendingConfirmation"`
|
||||||
ClosedAftersales int64 `json:"closedAftersales"`
|
ClosedAftersales int64 `json:"closedAftersales"`
|
||||||
RejectedAftersales int64 `json:"rejectedAftersales"`
|
RejectedAftersales int64 `json:"rejectedAftersales"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// EmployeeSerial 员工序列号模型
|
// EmployeeSerial 员工序列号模型
|
||||||
@@ -209,12 +223,14 @@ type EmployeeSerial struct {
|
|||||||
CompanyName string `gorm:"index;size:255" json:"companyName"`
|
CompanyName string `gorm:"index;size:255" json:"companyName"`
|
||||||
Position string `gorm:"size:255" json:"position"`
|
Position string `gorm:"size:255" json:"position"`
|
||||||
EmployeeName string `gorm:"size:255" json:"employeeName"`
|
EmployeeName string `gorm:"size:255" json:"employeeName"`
|
||||||
|
EmployeeID *uint `gorm:"index" json:"employeeId,omitempty"`
|
||||||
IsActive bool `gorm:"default:true" json:"isActive"`
|
IsActive bool `gorm:"default:true" json:"isActive"`
|
||||||
CreatedBy *uint `json:"createdBy"`
|
CreatedBy *uint `json:"createdBy"`
|
||||||
CreatedAt time.Time `json:"createdAt"`
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
UpdatedAt time.Time `json:"updatedAt"`
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
DeletedAt gorm.DeletedAt `gorm:"index" json:"-"`
|
||||||
User *User `gorm:"foreignKey:CreatedBy" json:"user,omitempty"`
|
User *User `gorm:"foreignKey:CreatedBy" json:"user,omitempty"`
|
||||||
|
Employee *User `gorm:"foreignKey:EmployeeID" json:"employee,omitempty"`
|
||||||
Company *Company `gorm:"foreignKey:CompanyName;references:CompanyName" json:"company,omitempty"`
|
Company *Company `gorm:"foreignKey:CompanyName;references:CompanyName" json:"company,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+13
-9
@@ -38,9 +38,9 @@ func SetupAPIRoutes(r *gin.RouterGroup) {
|
|||||||
serialsRoutes.PATCH("/:serialNumber", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), serialsController.Update)
|
serialsRoutes.PATCH("/:serialNumber", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), serialsController.Update)
|
||||||
serialsRoutes.POST("/generate", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), serialsController.Generate)
|
serialsRoutes.POST("/generate", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), serialsController.Generate)
|
||||||
serialsRoutes.POST("/generate-with-prefix", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), serialsController.GenerateWithPrefix)
|
serialsRoutes.POST("/generate-with-prefix", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), serialsController.GenerateWithPrefix)
|
||||||
serialsRoutes.POST("/:serialNumber/qrcode", middleware.JWTAuthMiddleware(), serialsController.GenerateQRCode)
|
serialsRoutes.POST("/:serialNumber/qrcode", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), serialsController.GenerateQRCode)
|
||||||
serialsRoutes.GET("/:serialNumber/query", serialsController.Query)
|
serialsRoutes.GET("/:serialNumber/query", serialsController.Query)
|
||||||
serialsRoutes.GET("/", middleware.JWTAuthMiddleware(), serialsController.FindAll)
|
serialsRoutes.GET("/", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), serialsController.FindAll)
|
||||||
serialsRoutes.PUT("/:serialNumber", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), serialsController.Update)
|
serialsRoutes.PUT("/:serialNumber", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), serialsController.Update)
|
||||||
serialsRoutes.POST("/:serialNumber/revoke", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), serialsController.Revoke)
|
serialsRoutes.POST("/:serialNumber/revoke", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), serialsController.Revoke)
|
||||||
}
|
}
|
||||||
@@ -66,9 +66,9 @@ func SetupAPIRoutes(r *gin.RouterGroup) {
|
|||||||
{
|
{
|
||||||
employeeSerialsRoutes.PATCH("/:serialNumber", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), employeeSerialsController.Update)
|
employeeSerialsRoutes.PATCH("/:serialNumber", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), employeeSerialsController.Update)
|
||||||
employeeSerialsRoutes.POST("/generate", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), employeeSerialsController.Generate)
|
employeeSerialsRoutes.POST("/generate", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), employeeSerialsController.Generate)
|
||||||
employeeSerialsRoutes.POST("/:serialNumber/qrcode", middleware.JWTAuthMiddleware(), employeeSerialsController.GenerateQRCode)
|
employeeSerialsRoutes.POST("/:serialNumber/qrcode", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), employeeSerialsController.GenerateQRCode)
|
||||||
employeeSerialsRoutes.GET("/:serialNumber/query", employeeSerialsController.Query)
|
employeeSerialsRoutes.GET("/:serialNumber/query", employeeSerialsController.Query)
|
||||||
employeeSerialsRoutes.GET("/", middleware.JWTAuthMiddleware(), employeeSerialsController.FindAll)
|
employeeSerialsRoutes.GET("/", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), employeeSerialsController.FindAll)
|
||||||
employeeSerialsRoutes.PUT("/:serialNumber", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), employeeSerialsController.Update)
|
employeeSerialsRoutes.PUT("/:serialNumber", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), employeeSerialsController.Update)
|
||||||
employeeSerialsRoutes.POST("/:serialNumber/revoke", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), employeeSerialsController.Revoke)
|
employeeSerialsRoutes.POST("/:serialNumber/revoke", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), employeeSerialsController.Revoke)
|
||||||
employeeSerialsRoutes.DELETE("/:serialNumber", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), employeeSerialsController.Delete)
|
employeeSerialsRoutes.DELETE("/:serialNumber", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), employeeSerialsController.Delete)
|
||||||
@@ -76,14 +76,18 @@ func SetupAPIRoutes(r *gin.RouterGroup) {
|
|||||||
|
|
||||||
// 用户管理路由(仅管理员)
|
// 用户管理路由(仅管理员)
|
||||||
usersController := controllers.NewUsersController()
|
usersController := controllers.NewUsersController()
|
||||||
|
employeesRoutes := r.Group("/employees")
|
||||||
|
{
|
||||||
|
employeesRoutes.POST("", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), usersController.Create)
|
||||||
|
employeesRoutes.GET("", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), usersController.FindAll)
|
||||||
|
employeesRoutes.PATCH("/:id", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), usersController.Update)
|
||||||
|
employeesRoutes.POST("/:id/reset-password", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), usersController.ResetPassword)
|
||||||
|
employeesRoutes.DELETE("/:id", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), usersController.Delete)
|
||||||
|
}
|
||||||
|
|
||||||
usersRoutes := r.Group("/users")
|
usersRoutes := r.Group("/users")
|
||||||
{
|
{
|
||||||
usersRoutes.GET("/assignable", middleware.JWTAuthMiddleware(), middleware.TechnicianMiddleware(), usersController.FindAssignable)
|
usersRoutes.GET("/assignable", middleware.JWTAuthMiddleware(), middleware.TechnicianMiddleware(), usersController.FindAssignable)
|
||||||
usersRoutes.POST("", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), usersController.Create)
|
|
||||||
usersRoutes.GET("", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), usersController.FindAll)
|
|
||||||
usersRoutes.PATCH("/:id", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), usersController.Update)
|
|
||||||
usersRoutes.POST("/:id/reset-password", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), usersController.ResetPassword)
|
|
||||||
usersRoutes.DELETE("/:id", middleware.JWTAuthMiddleware(), middleware.AdminMiddleware(), usersController.Delete)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 售后工单路由
|
// 售后工单路由
|
||||||
|
|||||||
+24
-12
@@ -23,6 +23,12 @@ func (s *AuthService) ValidateUser(username string, password string) (*models.Us
|
|||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return nil, errors.New("用户名或密码不正确")
|
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))
|
err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -56,12 +62,15 @@ func (s *AuthService) GetProfile(userId uint) (*models.UserDTO, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &models.UserDTO{
|
return &models.UserDTO{
|
||||||
ID: user.ID,
|
ID: user.ID,
|
||||||
Username: user.Username,
|
Username: user.Username,
|
||||||
Name: user.Name,
|
Name: user.Name,
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
Role: user.Role,
|
Phone: user.Phone,
|
||||||
CreatedAt: user.CreatedAt,
|
EmployeeNo: user.EmployeeNo,
|
||||||
|
Position: user.Position,
|
||||||
|
Role: user.Role,
|
||||||
|
CreatedAt: user.CreatedAt,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,11 +118,14 @@ func (s *AuthService) UpdateProfile(userId uint, name string, email string) (*mo
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &models.UserDTO{
|
return &models.UserDTO{
|
||||||
ID: user.ID,
|
ID: user.ID,
|
||||||
Username: user.Username,
|
Username: user.Username,
|
||||||
Name: user.Name,
|
Name: user.Name,
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
Role: user.Role,
|
Phone: user.Phone,
|
||||||
CreatedAt: user.CreatedAt,
|
EmployeeNo: user.EmployeeNo,
|
||||||
|
Position: user.Position,
|
||||||
|
Role: user.Role,
|
||||||
|
CreatedAt: user.CreatedAt,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,11 +16,14 @@ import (
|
|||||||
|
|
||||||
"git.beifan.cn/trace-system/backend-go/database"
|
"git.beifan.cn/trace-system/backend-go/database"
|
||||||
"git.beifan.cn/trace-system/backend-go/models"
|
"git.beifan.cn/trace-system/backend-go/models"
|
||||||
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// EmployeeSerialsService 员工序列号服务
|
// EmployeeSerialsService 员工序列号服务
|
||||||
type EmployeeSerialsService struct{}
|
type EmployeeSerialsService struct{}
|
||||||
|
|
||||||
|
const defaultEmployeeCompanyName = "内部员工"
|
||||||
|
|
||||||
// Generate 生成员工序列号
|
// Generate 生成员工序列号
|
||||||
func (s *EmployeeSerialsService) Generate(
|
func (s *EmployeeSerialsService) Generate(
|
||||||
companyName string,
|
companyName string,
|
||||||
@@ -29,18 +32,62 @@ func (s *EmployeeSerialsService) Generate(
|
|||||||
quantity int,
|
quantity int,
|
||||||
userId uint,
|
userId uint,
|
||||||
serialPrefix string,
|
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) {
|
) ([]models.EmployeeSerial, error) {
|
||||||
var serials []models.EmployeeSerial
|
var serials []models.EmployeeSerial
|
||||||
|
|
||||||
// 检查公司是否存在,不存在则创建
|
// 检查公司是否存在,不存在则创建
|
||||||
var company models.Company
|
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 {
|
if result.Error != nil {
|
||||||
company = models.Company{
|
company = models.Company{
|
||||||
CompanyName: companyName,
|
CompanyName: companyName,
|
||||||
IsActive: true,
|
IsActive: true,
|
||||||
}
|
}
|
||||||
result = database.DB.Create(&company)
|
result = db.Create(&company)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return nil, fmt.Errorf("创建公司失败: %w", result.Error)
|
return nil, fmt.Errorf("创建公司失败: %w", result.Error)
|
||||||
}
|
}
|
||||||
@@ -68,7 +115,7 @@ func (s *EmployeeSerialsService) Generate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
var existingSerial models.EmployeeSerial
|
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 {
|
if checkResult.Error != nil {
|
||||||
serialNumbers[serialNumber] = true
|
serialNumbers[serialNumber] = true
|
||||||
i++
|
i++
|
||||||
@@ -81,6 +128,7 @@ func (s *EmployeeSerialsService) Generate(
|
|||||||
CompanyName: companyName,
|
CompanyName: companyName,
|
||||||
Position: position,
|
Position: position,
|
||||||
EmployeeName: employeeName,
|
EmployeeName: employeeName,
|
||||||
|
EmployeeID: employeeID,
|
||||||
CreatedBy: &userId,
|
CreatedBy: &userId,
|
||||||
IsActive: true,
|
IsActive: true,
|
||||||
}
|
}
|
||||||
@@ -88,7 +136,7 @@ func (s *EmployeeSerialsService) Generate(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 保存到数据库
|
// 保存到数据库
|
||||||
result = database.DB.Create(&serials)
|
result = db.Create(&serials)
|
||||||
if result.Error != nil {
|
if result.Error != nil {
|
||||||
return nil, fmt.Errorf("保存员工序列号失败: %w", result.Error)
|
return nil, fmt.Errorf("保存员工序列号失败: %w", result.Error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ func TestAuthService_ValidateUser_Success(t *testing.T) {
|
|||||||
Password: string(password),
|
Password: string(password),
|
||||||
Name: "测试用户",
|
Name: "测试用户",
|
||||||
Email: "test@example.com",
|
Email: "test@example.com",
|
||||||
Role: "user",
|
Role: "technician",
|
||||||
}
|
}
|
||||||
database.DB.Create(&user)
|
database.DB.Create(&user)
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ func TestAuthService_ValidateUser_WrongPassword(t *testing.T) {
|
|||||||
Password: string(password),
|
Password: string(password),
|
||||||
Name: "测试用户2",
|
Name: "测试用户2",
|
||||||
Email: "test2@example.com",
|
Email: "test2@example.com",
|
||||||
Role: "user",
|
Role: "technician",
|
||||||
}
|
}
|
||||||
database.DB.Create(&user)
|
database.DB.Create(&user)
|
||||||
|
|
||||||
@@ -87,6 +87,25 @@ func TestAuthService_ValidateUser_WrongPassword(t *testing.T) {
|
|||||||
database.DB.Unscoped().Delete(&user)
|
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) {
|
func TestAuthService_ValidateUser_UserNotFound(t *testing.T) {
|
||||||
authService := AuthService{}
|
authService := AuthService{}
|
||||||
_, err := authService.ValidateUser("nonexistent", "password")
|
_, err := authService.ValidateUser("nonexistent", "password")
|
||||||
@@ -98,7 +117,7 @@ func TestAuthService_GenerateToken_Success(t *testing.T) {
|
|||||||
user := &models.User{
|
user := &models.User{
|
||||||
ID: 1,
|
ID: 1,
|
||||||
Username: "testuser",
|
Username: "testuser",
|
||||||
Role: "user",
|
Role: "technician",
|
||||||
}
|
}
|
||||||
|
|
||||||
authService := AuthService{}
|
authService := AuthService{}
|
||||||
@@ -117,7 +136,7 @@ func TestAuthService_GetProfile_Success(t *testing.T) {
|
|||||||
Password: string(password),
|
Password: string(password),
|
||||||
Name: "测试用户3",
|
Name: "测试用户3",
|
||||||
Email: "test3@example.com",
|
Email: "test3@example.com",
|
||||||
Role: "user",
|
Role: "technician",
|
||||||
}
|
}
|
||||||
database.DB.Create(&user)
|
database.DB.Create(&user)
|
||||||
|
|
||||||
@@ -140,7 +159,7 @@ func TestAuthService_ChangePassword_Success(t *testing.T) {
|
|||||||
Password: string(password),
|
Password: string(password),
|
||||||
Name: "测试用户4",
|
Name: "测试用户4",
|
||||||
Email: "test4@example.com",
|
Email: "test4@example.com",
|
||||||
Role: "user",
|
Role: "technician",
|
||||||
}
|
}
|
||||||
database.DB.Create(&user)
|
database.DB.Create(&user)
|
||||||
|
|
||||||
@@ -165,7 +184,7 @@ func TestAuthService_ChangePassword_WrongCurrentPassword(t *testing.T) {
|
|||||||
Password: string(password),
|
Password: string(password),
|
||||||
Name: "测试用户5",
|
Name: "测试用户5",
|
||||||
Email: "test5@example.com",
|
Email: "test5@example.com",
|
||||||
Role: "user",
|
Role: "technician",
|
||||||
}
|
}
|
||||||
database.DB.Create(&user)
|
database.DB.Create(&user)
|
||||||
|
|
||||||
@@ -185,7 +204,7 @@ func TestAuthService_UpdateProfile_Success(t *testing.T) {
|
|||||||
Password: string(password),
|
Password: string(password),
|
||||||
Name: "测试用户6",
|
Name: "测试用户6",
|
||||||
Email: "test6@example.com",
|
Email: "test6@example.com",
|
||||||
Role: "user",
|
Role: "technician",
|
||||||
}
|
}
|
||||||
database.DB.Create(&user)
|
database.DB.Create(&user)
|
||||||
|
|
||||||
|
|||||||
+111
-19
@@ -17,18 +17,57 @@ type UsersService struct{}
|
|||||||
|
|
||||||
func toUserDTO(user models.User) models.UserDTO {
|
func toUserDTO(user models.User) models.UserDTO {
|
||||||
return models.UserDTO{
|
return models.UserDTO{
|
||||||
ID: user.ID,
|
ID: user.ID,
|
||||||
Username: user.Username,
|
Username: user.Username,
|
||||||
Name: user.Name,
|
Name: user.Name,
|
||||||
Email: user.Email,
|
Email: user.Email,
|
||||||
Role: user.Role,
|
Phone: user.Phone,
|
||||||
CreatedAt: user.CreatedAt,
|
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 创建用户
|
// Create 创建用户
|
||||||
func (s *UsersService) Create(dto models.CreateUserDTO) (*models.UserDTO, error) {
|
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)
|
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
|
var existing models.User
|
||||||
err := database.DB.Where("username = ?", username).First(&existing).Error
|
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)
|
return nil, fmt.Errorf("查询用户失败: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
hashed, err := bcrypt.GenerateFromPassword([]byte(dto.Password), bcrypt.DefaultCost)
|
err = database.DB.Where("employee_no = ?", employeeNo).First(&existing).Error
|
||||||
if err != nil {
|
if err == nil {
|
||||||
return nil, fmt.Errorf("密码加密失败: %w", err)
|
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{
|
user := models.User{
|
||||||
Username: username,
|
Username: username,
|
||||||
Password: string(hashed),
|
Password: hashed,
|
||||||
Name: dto.Name,
|
Name: name,
|
||||||
Email: dto.Email,
|
Email: dto.Email,
|
||||||
Role: dto.Role,
|
Phone: phone,
|
||||||
|
EmployeeNo: employeeNo,
|
||||||
|
Position: position,
|
||||||
|
Role: role,
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := database.DB.Create(&user).Error; err != nil {
|
err = database.DB.Transaction(func(tx *gorm.DB) error {
|
||||||
return nil, fmt.Errorf("创建用户失败: %w", err)
|
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)
|
dtoOut := toUserDTO(user)
|
||||||
@@ -65,13 +131,14 @@ func (s *UsersService) FindAll(page int, limit int, role string, search string)
|
|||||||
var users []models.User
|
var users []models.User
|
||||||
var total int64
|
var total int64
|
||||||
|
|
||||||
db := database.DB.Model(&models.User{})
|
db := database.DB.Model(&models.User{}).Preload("EmployeeSerials")
|
||||||
if role != "" {
|
if role != "" {
|
||||||
db = db.Where("role = ?", role)
|
db = db.Where("role = ?", role)
|
||||||
}
|
}
|
||||||
if search != "" {
|
if search != "" {
|
||||||
pattern := "%" + 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 {
|
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 != "" {
|
if dto.Name != "" {
|
||||||
user.Name = dto.Name
|
user.Name = strings.TrimSpace(dto.Name)
|
||||||
}
|
}
|
||||||
if dto.Email != "" {
|
if dto.Email != "" {
|
||||||
user.Email = 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 dto.Role != "" {
|
||||||
|
if !isValidEmployeeRole(dto.Role) {
|
||||||
|
return nil, errors.New("角色不正确")
|
||||||
|
}
|
||||||
// 防止管理员把自己降级
|
// 防止管理员把自己降级
|
||||||
if user.ID == currentUserId && user.Role == "admin" && dto.Role != "admin" {
|
if user.ID == currentUserId && user.Role == "admin" && dto.Role != "admin" {
|
||||||
return nil, errors.New("不能修改自己的管理员角色")
|
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 {
|
if err := database.DB.First(&user, userId).Error; err != nil {
|
||||||
return errors.New("用户不存在")
|
return errors.New("用户不存在")
|
||||||
}
|
}
|
||||||
|
if !hasBackendAccess(user.Role) {
|
||||||
|
return errors.New("员工无后台登录权限,不能设置密码")
|
||||||
|
}
|
||||||
|
|
||||||
hashed, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
|
hashed, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.DefaultCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -13,18 +13,59 @@ import (
|
|||||||
func TestUsersService_Create_Success(t *testing.T) {
|
func TestUsersService_Create_Success(t *testing.T) {
|
||||||
svc := UsersService{}
|
svc := UsersService{}
|
||||||
dto, err := svc.Create(models.CreateUserDTO{
|
dto, err := svc.Create(models.CreateUserDTO{
|
||||||
Username: "users_create_ok",
|
Password: "password123",
|
||||||
Password: "password123",
|
Name: "新技术员",
|
||||||
Name: "新技术员",
|
Email: "new@example.com",
|
||||||
Email: "new@example.com",
|
Phone: "13800000001",
|
||||||
Role: "technician",
|
EmployeeNo: "users_create_ok",
|
||||||
|
Position: "技术员",
|
||||||
|
Role: "technician",
|
||||||
})
|
})
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, dto)
|
assert.NotNil(t, dto)
|
||||||
assert.Equal(t, "users_create_ok", dto.Username)
|
assert.Equal(t, "users_create_ok", dto.Username)
|
||||||
assert.Equal(t, "technician", dto.Role)
|
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("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) {
|
func TestUsersService_Create_DuplicateUsername(t *testing.T) {
|
||||||
@@ -39,10 +80,13 @@ func TestUsersService_Create_DuplicateUsername(t *testing.T) {
|
|||||||
|
|
||||||
svc := UsersService{}
|
svc := UsersService{}
|
||||||
_, err := svc.Create(models.CreateUserDTO{
|
_, err := svc.Create(models.CreateUserDTO{
|
||||||
Username: "users_create_dup",
|
Username: "users_create_dup",
|
||||||
Password: "password123",
|
Password: "password123",
|
||||||
Name: "duplicate",
|
Name: "duplicate",
|
||||||
Role: "technician",
|
Phone: "13800000004",
|
||||||
|
EmployeeNo: "users_create_dup_2",
|
||||||
|
Position: "技术员",
|
||||||
|
Role: "technician",
|
||||||
})
|
})
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
assert.Contains(t, err.Error(), "用户名已存在")
|
assert.Contains(t, err.Error(), "用户名已存在")
|
||||||
@@ -164,7 +208,7 @@ func TestUsersService_Delete_BlocksLastAdmin(t *testing.T) {
|
|||||||
func TestUsersService_FindAssignable_ReturnsAdminAndTechnician(t *testing.T) {
|
func TestUsersService_FindAssignable_ReturnsAdminAndTechnician(t *testing.T) {
|
||||||
a := models.User{Username: "assignable_admin", Password: "x", Name: "A", Role: "admin"}
|
a := models.User{Username: "assignable_admin", Password: "x", Name: "A", Role: "admin"}
|
||||||
tech := models.User{Username: "assignable_tech", Password: "x", Name: "T", Role: "technician"}
|
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(&a)
|
||||||
database.DB.Create(&tech)
|
database.DB.Create(&tech)
|
||||||
database.DB.Create(&plain)
|
database.DB.Create(&plain)
|
||||||
@@ -189,7 +233,7 @@ func TestUsersService_FindAssignable_ReturnsAdminAndTechnician(t *testing.T) {
|
|||||||
func TestUsersService_FindAll_FilterByRole(t *testing.T) {
|
func TestUsersService_FindAll_FilterByRole(t *testing.T) {
|
||||||
tech1 := models.User{Username: "findall_tech1", Password: "x", Name: "T1", Role: "technician"}
|
tech1 := models.User{Username: "findall_tech1", Password: "x", Name: "T1", Role: "technician"}
|
||||||
tech2 := models.User{Username: "findall_tech2", Password: "x", Name: "T2", 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(&tech1)
|
||||||
database.DB.Create(&tech2)
|
database.DB.Create(&tech2)
|
||||||
database.DB.Create(&user1)
|
database.DB.Create(&user1)
|
||||||
|
|||||||
+4
-4
@@ -73,11 +73,11 @@ func createTestUsers() {
|
|||||||
Role: "admin",
|
Role: "admin",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Username: "user1",
|
Username: "employee1",
|
||||||
Password: string(testPassword),
|
Password: string(testPassword),
|
||||||
Name: "普通用户",
|
Name: "普通员工",
|
||||||
Email: "user1@example.com",
|
Email: "employee1@example.com",
|
||||||
Role: "user",
|
Role: "employee",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user