Add employee code assignment function

This commit is contained in:
2026-03-02 09:38:03 +08:00
parent 58b1ac1126
commit c29cbdf847
9 changed files with 962 additions and 9 deletions

View File

@@ -0,0 +1,260 @@
package services
import (
"crypto/rand"
"encoding/base64"
"encoding/hex"
"errors"
"fmt"
"os"
"strings"
"time"
"github.com/google/uuid"
qr "github.com/yeqown/go-qrcode/v2"
"github.com/yeqown/go-qrcode/writer/standard"
"git.beifan.cn/trace-system/backend-go/database"
"git.beifan.cn/trace-system/backend-go/models"
)
// EmployeeSerialsService 员工序列号服务
type EmployeeSerialsService struct{}
// Generate 生成员工序列号
func (s *EmployeeSerialsService) Generate(
companyName string,
department string,
employeeName string,
quantity int,
userId uint,
) ([]models.EmployeeSerial, error) {
var serials []models.EmployeeSerial
// 检查公司是否存在,不存在则创建
var company models.Company
result := database.DB.Where("company_name = ?", companyName).First(&company)
if result.Error != nil {
company = models.Company{
CompanyName: companyName,
IsActive: true,
}
result = database.DB.Create(&company)
if result.Error != nil {
return nil, fmt.Errorf("创建公司失败: %w", result.Error)
}
}
// 生成序列号前缀 (EMP + 年份后两位)
serialPrefix := fmt.Sprintf("EMP%d", time.Now().Year()%100)
// 预生成所有序列号
serialNumbers := make(map[string]bool)
for i := 0; i < quantity; {
randomBytes := make([]byte, 3)
if _, err := rand.Read(randomBytes); err != nil {
return nil, fmt.Errorf("生成随机数失败: %w", err)
}
randomPart := hex.EncodeToString(randomBytes)[:6]
serialNumber := fmt.Sprintf("%s%s", serialPrefix, randomPart)
if serialNumbers[serialNumber] {
continue
}
var existingSerial models.EmployeeSerial
checkResult := database.DB.Where("serial_number = ?", serialNumber).First(&existingSerial)
if checkResult.Error != nil {
serialNumbers[serialNumber] = true
i++
}
}
for serialNumber := range serialNumbers {
serial := models.EmployeeSerial{
SerialNumber: strings.ToUpper(serialNumber),
CompanyName: companyName,
Department: department,
EmployeeName: employeeName,
CreatedBy: &userId,
IsActive: true,
}
serials = append(serials, serial)
}
// 保存到数据库
result = database.DB.Create(&serials)
if result.Error != nil {
return nil, fmt.Errorf("保存员工序列号失败: %w", result.Error)
}
return serials, nil
}
// Query 查询员工序列号信息
func (s *EmployeeSerialsService) Query(serialNumber string) (*models.EmployeeSerial, error) {
var serial models.EmployeeSerial
result := database.DB.Preload("User").Where("serial_number = ?", strings.ToUpper(serialNumber)).First(&serial)
if result.Error != nil {
return nil, fmt.Errorf("查询员工序列号失败: %w", errors.New("序列号不存在"))
}
return &serial, nil
}
// FindAll 获取员工序列号列表
func (s *EmployeeSerialsService) FindAll(page int, limit int, search string) ([]models.EmployeeSerial, int, int, error) {
var serials []models.EmployeeSerial
var total int64
offset := (page - 1) * limit
db := database.DB.Preload("User")
// 搜索条件
if search != "" {
db = db.Where("serial_number LIKE ? OR company_name LIKE ? OR department LIKE ? OR employee_name LIKE ?",
"%"+search+"%", "%"+search+"%", "%"+search+"%", "%"+search+"%")
}
// 获取总数
countQuery := db.Model(&models.EmployeeSerial{})
if search != "" {
countQuery = countQuery.Where("serial_number LIKE ? OR company_name LIKE ? OR department LIKE ? OR employee_name LIKE ?",
"%"+search+"%", "%"+search+"%", "%"+search+"%", "%"+search+"%")
}
countQuery.Count(&total)
// 分页查询
result := db.Model(&models.EmployeeSerial{}).Order("created_at DESC").Offset(offset).Limit(limit).Find(&serials)
if result.Error != nil {
return nil, 0, 0, fmt.Errorf("查询员工序列号列表失败: %w", result.Error)
}
totalPages := (int(total) + limit - 1) / limit
return serials, int(total), totalPages, nil
}
// Update 更新员工序列号信息
func (s *EmployeeSerialsService) Update(serialNumber string, updateData models.UpdateEmployeeSerialDTO) (*models.EmployeeSerial, error) {
var serial models.EmployeeSerial
result := database.DB.Where("serial_number = ?", strings.ToUpper(serialNumber)).First(&serial)
if result.Error != nil {
return nil, fmt.Errorf("查询员工序列号失败: %w", errors.New("序列号不存在"))
}
if updateData.CompanyName != "" {
// 检查公司是否存在
var company models.Company
companyResult := database.DB.Where("company_name = ?", updateData.CompanyName).First(&company)
if companyResult.Error != nil {
company = models.Company{
CompanyName: updateData.CompanyName,
IsActive: true,
}
database.DB.Create(&company)
}
serial.CompanyName = updateData.CompanyName
}
if updateData.Department != "" {
serial.Department = updateData.Department
}
if updateData.EmployeeName != "" {
serial.EmployeeName = updateData.EmployeeName
}
if updateData.IsActive != nil {
serial.IsActive = *updateData.IsActive
}
result = database.DB.Save(&serial)
if result.Error != nil {
return nil, fmt.Errorf("更新员工序列号失败: %w", result.Error)
}
return &serial, nil
}
// Revoke 吊销员工序列号
func (s *EmployeeSerialsService) Revoke(serialNumber string) error {
var serial models.EmployeeSerial
result := database.DB.Where("serial_number = ?", strings.ToUpper(serialNumber)).First(&serial)
if result.Error != nil {
return fmt.Errorf("查询员工序列号失败: %w", errors.New("序列号不存在"))
}
if !serial.IsActive {
return fmt.Errorf("员工序列号状态无效: %w", errors.New("序列号已被吊销"))
}
serial.IsActive = false
result = database.DB.Save(&serial)
if result.Error != nil {
return fmt.Errorf("吊销员工序列号失败: %w", result.Error)
}
return nil
}
// GenerateQRCode 生成员工二维码
func (s *EmployeeSerialsService) GenerateQRCode(
serialNumber string,
baseUrl string,
requestHost string,
protocol string,
) (string, string, error) {
var serial models.EmployeeSerial
result := database.DB.Preload("User").Where("serial_number = ?", strings.ToUpper(serialNumber)).First(&serial)
if result.Error != nil {
return "", "", fmt.Errorf("查询员工序列号失败: %w", errors.New("序列号不存在"))
}
if !serial.IsActive {
return "", "", fmt.Errorf("员工序列号状态无效: %w", errors.New("序列号已被禁用"))
}
// 确定查询 URL
if baseUrl == "" {
baseUrl = fmt.Sprintf("%s://%s/query.html", protocol, requestHost)
}
var queryURL string
if strings.Contains(baseUrl, "?") {
queryURL = fmt.Sprintf("%s&serial=%s", baseUrl, serial.SerialNumber)
} else {
queryURL = fmt.Sprintf("%s?serial=%s", baseUrl, serial.SerialNumber)
}
// 生成二维码到临时文件
filePath := fmt.Sprintf("temp_qr_%s.png", uuid.New().String())
writer, err := standard.New(filePath, standard.WithQRWidth(6))
if err != nil {
return "", "", fmt.Errorf("二维码写入器创建失败: %w", err)
}
qrc, errCode := qr.New(queryURL)
if errCode != nil {
os.Remove(filePath)
return "", "", fmt.Errorf("二维码创建失败: %w", errCode)
}
if errSave := qrc.Save(writer); errSave != nil {
os.Remove(filePath)
return "", "", fmt.Errorf("二维码保存失败: %w", errSave)
}
// 读取文件并转换为 Base64
fileBytes, err := os.ReadFile(filePath)
if err != nil {
os.Remove(filePath)
return "", "", fmt.Errorf("读取二维码文件失败: %w", err)
}
os.Remove(filePath)
qrCodeBase64 := base64.StdEncoding.EncodeToString(fileBytes)
return qrCodeBase64, queryURL, nil
}

View File

@@ -31,12 +31,14 @@ func TestMain(m *testing.M) {
database.DB.Unscoped().Where("1 = 1").Delete(&models.User{})
database.DB.Unscoped().Where("1 = 1").Delete(&models.Company{})
database.DB.Unscoped().Where("1 = 1").Delete(&models.Serial{})
database.DB.Unscoped().Where("1 = 1").Delete(&models.EmployeeSerial{})
exitCode := m.Run()
database.DB.Unscoped().Where("1 = 1").Delete(&models.User{})
database.DB.Unscoped().Where("1 = 1").Delete(&models.Company{})
database.DB.Unscoped().Where("1 = 1").Delete(&models.Serial{})
database.DB.Unscoped().Where("1 = 1").Delete(&models.EmployeeSerial{})
os.Exit(exitCode)
}
@@ -411,3 +413,363 @@ func TestSerialsService_Update_Success(t *testing.T) {
database.DB.Unscoped().Where("company_name = ?", "UpdateCompany").Delete(&models.Company{})
database.DB.Unscoped().Delete(&user)
}
// ==================== EmployeeSerialsService 测试 ====================
func TestEmployeeSerialsService_Generate_Success(t *testing.T) {
var user models.User
password, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
user = models.User{
Username: "empadmin1",
Password: string(password),
Name: "员工管理员1",
Email: "empadmin1@example.com",
Role: "admin",
}
database.DB.Create(&user)
service := EmployeeSerialsService{}
serials, err := service.Generate("EmpTestCompany", "技术部", "张三", 5, user.ID)
assert.NoError(t, err)
assert.Len(t, serials, 5)
for _, serial := range serials {
assert.Equal(t, "EmpTestCompany", serial.CompanyName)
assert.Equal(t, "技术部", serial.Department)
assert.Equal(t, "张三", serial.EmployeeName)
assert.True(t, serial.IsActive)
assert.True(t, strings.HasPrefix(serial.SerialNumber, "EMP"))
database.DB.Unscoped().Delete(&serial)
}
database.DB.Unscoped().Where("company_name = ?", "EmpTestCompany").Delete(&models.Company{})
database.DB.Unscoped().Delete(&user)
}
func TestEmployeeSerialsService_Generate_CreateNewCompany(t *testing.T) {
var user models.User
password, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
user = models.User{
Username: "empadmin2",
Password: string(password),
Name: "员工管理员2",
Email: "empadmin2@example.com",
Role: "admin",
}
database.DB.Create(&user)
service := EmployeeSerialsService{}
serials, err := service.Generate("NewEmpCompany", "市场部", "李四", 3, user.ID)
assert.NoError(t, err)
assert.Len(t, serials, 3)
var company models.Company
result := database.DB.Where("company_name = ?", "NewEmpCompany").First(&company)
assert.NoError(t, result.Error)
assert.Equal(t, "NewEmpCompany", company.CompanyName)
assert.True(t, company.IsActive)
for _, serial := range serials {
database.DB.Unscoped().Delete(&serial)
}
database.DB.Unscoped().Where("company_name = ?", "NewEmpCompany").Delete(&models.Company{})
database.DB.Unscoped().Delete(&user)
}
func TestEmployeeSerialsService_Query_Success(t *testing.T) {
var user models.User
password, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
user = models.User{
Username: "empadmin3",
Password: string(password),
Name: "员工管理员3",
Email: "empadmin3@example.com",
Role: "admin",
}
database.DB.Create(&user)
service := EmployeeSerialsService{}
serials, _ := service.Generate("QueryEmpCompany", "财务部", "王五", 1, user.ID)
serialNumber := serials[0].SerialNumber
result, err := service.Query(serialNumber)
assert.NoError(t, err)
assert.NotNil(t, result)
assert.Equal(t, strings.ToUpper(serialNumber), strings.ToUpper(result.SerialNumber))
assert.Equal(t, "QueryEmpCompany", result.CompanyName)
assert.Equal(t, "财务部", result.Department)
assert.Equal(t, "王五", result.EmployeeName)
assert.True(t, result.IsActive)
for _, serial := range serials {
database.DB.Unscoped().Delete(&serial)
}
database.DB.Unscoped().Where("company_name = ?", "QueryEmpCompany").Delete(&models.Company{})
database.DB.Unscoped().Delete(&user)
}
func TestEmployeeSerialsService_Query_NotFound(t *testing.T) {
service := EmployeeSerialsService{}
_, err := service.Query("NONEXISTENTEMP")
assert.Error(t, err)
assert.Contains(t, err.Error(), "序列号不存在")
}
func TestEmployeeSerialsService_FindAll_Success(t *testing.T) {
var user models.User
password, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
user = models.User{
Username: "empadmin4",
Password: string(password),
Name: "员工管理员4",
Email: "empadmin4@example.com",
Role: "admin",
}
database.DB.Create(&user)
service := EmployeeSerialsService{}
serials, _ := service.Generate("ListEmpCompany", "人事部", "赵六", 10, user.ID)
result, total, totalPages, err := service.FindAll(1, 5, "")
assert.NoError(t, err)
assert.Len(t, result, 5)
assert.GreaterOrEqual(t, total, 10)
assert.Greater(t, totalPages, 0)
for _, serial := range serials {
database.DB.Unscoped().Delete(&serial)
}
database.DB.Unscoped().Where("company_name = ?", "ListEmpCompany").Delete(&models.Company{})
database.DB.Unscoped().Delete(&user)
}
func TestEmployeeSerialsService_FindAll_WithSearch(t *testing.T) {
var user models.User
password, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
user = models.User{
Username: "empadmin5",
Password: string(password),
Name: "员工管理员5",
Email: "empadmin5@example.com",
Role: "admin",
}
database.DB.Create(&user)
service := EmployeeSerialsService{}
serials, _ := service.Generate("SearchEmpCompany", "研发部", "钱七", 5, user.ID)
result, _, _, err := service.FindAll(1, 10, "SearchEmpCompany")
assert.NoError(t, err)
assert.Greater(t, len(result), 0)
assert.Equal(t, "SearchEmpCompany", result[0].CompanyName)
result2, _, _, err2 := service.FindAll(1, 10, "研发部")
assert.NoError(t, err2)
assert.Greater(t, len(result2), 0)
result3, _, _, err3 := service.FindAll(1, 10, "钱七")
assert.NoError(t, err3)
assert.Greater(t, len(result3), 0)
for _, serial := range serials {
database.DB.Unscoped().Delete(&serial)
}
database.DB.Unscoped().Where("company_name = ?", "SearchEmpCompany").Delete(&models.Company{})
database.DB.Unscoped().Delete(&user)
}
func TestEmployeeSerialsService_Update_Success(t *testing.T) {
var user models.User
password, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
user = models.User{
Username: "empadmin6",
Password: string(password),
Name: "员工管理员6",
Email: "empadmin6@example.com",
Role: "admin",
}
database.DB.Create(&user)
service := EmployeeSerialsService{}
serials, _ := service.Generate("UpdateEmpCompany", "运营部", "孙八", 1, user.ID)
serialNumber := serials[0].SerialNumber
isActive := false
updateData := models.UpdateEmployeeSerialDTO{
CompanyName: "UpdatedEmpCompany",
Department: "新部门",
EmployeeName: "新名字",
IsActive: &isActive,
}
result, err := service.Update(serialNumber, updateData)
assert.NoError(t, err)
assert.NotNil(t, result)
assert.Equal(t, "UpdatedEmpCompany", result.CompanyName)
assert.Equal(t, "新部门", result.Department)
assert.Equal(t, "新名字", result.EmployeeName)
assert.False(t, result.IsActive)
for _, serial := range serials {
database.DB.Unscoped().Delete(&serial)
}
database.DB.Unscoped().Where("company_name = ?", "UpdateEmpCompany").Delete(&models.Company{})
database.DB.Unscoped().Where("company_name = ?", "UpdatedEmpCompany").Delete(&models.Company{})
database.DB.Unscoped().Delete(&user)
}
func TestEmployeeSerialsService_Update_NotFound(t *testing.T) {
service := EmployeeSerialsService{}
updateData := models.UpdateEmployeeSerialDTO{
CompanyName: "TestCompany",
}
_, err := service.Update("NONEXISTENT", updateData)
assert.Error(t, err)
assert.Contains(t, err.Error(), "序列号不存在")
}
func TestEmployeeSerialsService_Revoke_Success(t *testing.T) {
var user models.User
password, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
user = models.User{
Username: "empadmin7",
Password: string(password),
Name: "员工管理员7",
Email: "empadmin7@example.com",
Role: "admin",
}
database.DB.Create(&user)
service := EmployeeSerialsService{}
serials, _ := service.Generate("RevokeEmpCompany", "测试部", "周九", 1, user.ID)
serialNumber := serials[0].SerialNumber
err := service.Revoke(serialNumber)
assert.NoError(t, err)
var revokedSerial models.EmployeeSerial
database.DB.Where("serial_number = ?", serialNumber).First(&revokedSerial)
assert.False(t, revokedSerial.IsActive)
for _, serial := range serials {
database.DB.Unscoped().Delete(&serial)
}
database.DB.Unscoped().Where("company_name = ?", "RevokeEmpCompany").Delete(&models.Company{})
database.DB.Unscoped().Delete(&user)
}
func TestEmployeeSerialsService_Revoke_NotFound(t *testing.T) {
service := EmployeeSerialsService{}
err := service.Revoke("NONEXISTENT")
assert.Error(t, err)
assert.Contains(t, err.Error(), "序列号不存在")
}
func TestEmployeeSerialsService_Revoke_AlreadyRevoked(t *testing.T) {
var user models.User
password, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
user = models.User{
Username: "empadmin8",
Password: string(password),
Name: "员工管理员8",
Email: "empadmin8@example.com",
Role: "admin",
}
database.DB.Create(&user)
service := EmployeeSerialsService{}
serials, _ := service.Generate("RevokeEmpCompany2", "行政部", "吴十", 1, user.ID)
serialNumber := serials[0].SerialNumber
service.Revoke(serialNumber)
err := service.Revoke(serialNumber)
assert.Error(t, err)
assert.Contains(t, err.Error(), "已被吊销")
for _, serial := range serials {
database.DB.Unscoped().Delete(&serial)
}
database.DB.Unscoped().Where("company_name = ?", "RevokeEmpCompany2").Delete(&models.Company{})
database.DB.Unscoped().Delete(&user)
}
func TestEmployeeSerialsService_GenerateQRCode_Success(t *testing.T) {
var user models.User
password, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
user = models.User{
Username: "empadmin9",
Password: string(password),
Name: "员工管理员9",
Email: "empadmin9@example.com",
Role: "admin",
}
database.DB.Create(&user)
service := EmployeeSerialsService{}
serials, _ := service.Generate("QREmpCompany", "产品部", "郑十一", 1, user.ID)
serialNumber := serials[0].SerialNumber
qrCodeBase64, queryUrl, err := service.GenerateQRCode(serialNumber, "", "localhost:3000", "http")
assert.NoError(t, err)
assert.NotEmpty(t, qrCodeBase64)
assert.NotEmpty(t, queryUrl)
assert.Contains(t, queryUrl, serialNumber)
assert.Contains(t, queryUrl, "query.html")
for _, serial := range serials {
database.DB.Unscoped().Delete(&serial)
}
database.DB.Unscoped().Where("company_name = ?", "QREmpCompany").Delete(&models.Company{})
database.DB.Unscoped().Delete(&user)
}
func TestEmployeeSerialsService_GenerateQRCode_NotFound(t *testing.T) {
service := EmployeeSerialsService{}
_, _, err := service.GenerateQRCode("NONEXISTENT", "", "localhost:3000", "http")
assert.Error(t, err)
assert.Contains(t, err.Error(), "序列号不存在")
}
func TestEmployeeSerialsService_GenerateQRCode_Inactive(t *testing.T) {
var user models.User
password, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
user = models.User{
Username: "empadmin10",
Password: string(password),
Name: "员工管理员10",
Email: "empadmin10@example.com",
Role: "admin",
}
database.DB.Create(&user)
service := EmployeeSerialsService{}
serials, _ := service.Generate("QREmpCompany2", "设计部", "王十二", 1, user.ID)
serialNumber := serials[0].SerialNumber
service.Revoke(serialNumber)
_, _, err := service.GenerateQRCode(serialNumber, "", "localhost:3000", "http")
assert.Error(t, err)
assert.Contains(t, err.Error(), "已被禁用")
for _, serial := range serials {
database.DB.Unscoped().Delete(&serial)
}
database.DB.Unscoped().Where("company_name = ?", "QREmpCompany2").Delete(&models.Company{})
database.DB.Unscoped().Delete(&user)
}