refactor: remove company management APIs
This commit is contained in:
@@ -1,502 +0,0 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
|
||||
"git.beifan.cn/trace-system/backend-go/database"
|
||||
"git.beifan.cn/trace-system/backend-go/models"
|
||||
)
|
||||
|
||||
// CompaniesService 企业管理服务
|
||||
type CompaniesService struct{}
|
||||
|
||||
// FindAll 获取所有企业列表
|
||||
func (s *CompaniesService) FindAll(page int, limit int, search string) ([]models.Company, int, int, error) {
|
||||
var companies []models.Company
|
||||
var total int64
|
||||
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if limit < 1 {
|
||||
limit = 20
|
||||
}
|
||||
|
||||
offset := (page - 1) * limit
|
||||
db := database.DB.Model(&models.Company{})
|
||||
|
||||
if search != "" {
|
||||
db = db.Where("company_name LIKE ?", "%"+search+"%")
|
||||
}
|
||||
|
||||
if err := db.Count(&total).Error; err != nil {
|
||||
return nil, 0, 0, errors.New("查询企业总数失败")
|
||||
}
|
||||
|
||||
result := db.Preload("Serials").Order("updated_at DESC").Offset(offset).Limit(limit).Find(&companies)
|
||||
if result.Error != nil {
|
||||
return nil, 0, 0, errors.New("查询企业列表失败")
|
||||
}
|
||||
|
||||
totalPages := 0
|
||||
if total > 0 {
|
||||
totalPages = (int(total) + limit - 1) / limit
|
||||
}
|
||||
|
||||
return companies, int(total), totalPages, nil
|
||||
}
|
||||
|
||||
// FindOne 获取单个企业详情(含分页序列号)
|
||||
func (s *CompaniesService) FindOne(companyName string, page int, limit int) (map[string]any, error) {
|
||||
if page < 1 {
|
||||
page = 1
|
||||
}
|
||||
if limit < 1 {
|
||||
limit = 20
|
||||
}
|
||||
|
||||
var company models.Company
|
||||
if err := database.DB.Where("company_name = ?", companyName).First(&company).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, errors.New("企业不存在")
|
||||
}
|
||||
return nil, errors.New("查询企业失败")
|
||||
}
|
||||
|
||||
var allSerials []models.Serial
|
||||
if err := database.DB.Preload("User").Where("company_name = ?", companyName).Order("created_at DESC").Find(&allSerials).Error; err != nil {
|
||||
return nil, errors.New("查询企业序列号失败")
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
serialCount := len(allSerials)
|
||||
activeCount := 0
|
||||
disabledCount := 0
|
||||
expiredCount := 0
|
||||
|
||||
for _, serial := range allSerials {
|
||||
if !serial.IsActive {
|
||||
disabledCount++
|
||||
continue
|
||||
}
|
||||
if serial.ValidUntil != nil && serial.ValidUntil.Before(now) {
|
||||
expiredCount++
|
||||
continue
|
||||
}
|
||||
activeCount++
|
||||
}
|
||||
|
||||
offset := (page - 1) * limit
|
||||
end := offset + limit
|
||||
if offset > len(allSerials) {
|
||||
offset = len(allSerials)
|
||||
}
|
||||
if end > len(allSerials) {
|
||||
end = len(allSerials)
|
||||
}
|
||||
paginatedSerials := allSerials[offset:end]
|
||||
|
||||
serialItems := make([]map[string]any, 0, len(paginatedSerials))
|
||||
for _, serial := range paginatedSerials {
|
||||
createdBy := ""
|
||||
if serial.User != nil {
|
||||
createdBy = serial.User.Name
|
||||
}
|
||||
serialItems = append(serialItems, map[string]any{
|
||||
"serialNumber": serial.SerialNumber,
|
||||
"validUntil": serial.ValidUntil,
|
||||
"isActive": serial.IsActive,
|
||||
"createdAt": serial.CreatedAt,
|
||||
"createdBy": createdBy,
|
||||
})
|
||||
}
|
||||
|
||||
monthlyStatsMap := map[string]int{}
|
||||
for i := 11; i >= 0; i-- {
|
||||
date := time.Date(now.Year(), now.Month()-time.Month(i), 1, 0, 0, 0, 0, time.Local)
|
||||
monthKey := date.Format("2006-01")
|
||||
monthlyStatsMap[monthKey] = 0
|
||||
}
|
||||
for _, serial := range allSerials {
|
||||
monthKey := serial.CreatedAt.Format("2006-01")
|
||||
if _, ok := monthlyStatsMap[monthKey]; ok {
|
||||
monthlyStatsMap[monthKey]++
|
||||
}
|
||||
}
|
||||
|
||||
monthlyStats := make([]map[string]any, 0)
|
||||
for i := 11; i >= 0; i-- {
|
||||
date := time.Date(now.Year(), now.Month()-time.Month(i), 1, 0, 0, 0, 0, time.Local)
|
||||
monthKey := date.Format("2006-01")
|
||||
count := monthlyStatsMap[monthKey]
|
||||
if count > 0 {
|
||||
monthlyStats = append(monthlyStats, map[string]any{"month": monthKey, "count": count})
|
||||
}
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"companyName": company.CompanyName,
|
||||
"serialCount": serialCount,
|
||||
"activeCount": activeCount,
|
||||
"disabledCount": disabledCount,
|
||||
"expiredCount": expiredCount,
|
||||
"firstCreated": company.CreatedAt,
|
||||
"lastCreated": company.UpdatedAt,
|
||||
"status": map[bool]string{true: "active", false: "disabled"}[company.IsActive],
|
||||
"serials": serialItems,
|
||||
"monthlyStats": monthlyStats,
|
||||
"pagination": map[string]any{
|
||||
"page": page,
|
||||
"limit": limit,
|
||||
"total": serialCount,
|
||||
"totalPages": func() int {
|
||||
if serialCount == 0 {
|
||||
return 0
|
||||
}
|
||||
return (serialCount + limit - 1) / limit
|
||||
}(),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Create 创建企业
|
||||
func (s *CompaniesService) Create(companyName string) (*models.Company, error) {
|
||||
var existingCompany models.Company
|
||||
result := database.DB.Where("company_name = ?", companyName).First(&existingCompany)
|
||||
if result.Error == nil {
|
||||
return nil, errors.New("企业名称已存在")
|
||||
}
|
||||
|
||||
company := models.Company{
|
||||
CompanyName: companyName,
|
||||
IsActive: true,
|
||||
}
|
||||
|
||||
result = database.DB.Create(&company)
|
||||
if result.Error != nil {
|
||||
return nil, errors.New("创建企业失败")
|
||||
}
|
||||
|
||||
return &company, nil
|
||||
}
|
||||
|
||||
// Update 更新企业信息
|
||||
func (s *CompaniesService) Update(companyName string, newCompanyName string, isActive *bool) (*models.Company, error) {
|
||||
var company models.Company
|
||||
result := database.DB.Where("company_name = ?", companyName).First(&company)
|
||||
if result.Error != nil {
|
||||
return nil, errors.New("企业不存在")
|
||||
}
|
||||
|
||||
if newCompanyName == "" {
|
||||
newCompanyName = companyName
|
||||
}
|
||||
|
||||
if newCompanyName != companyName {
|
||||
var existingCompany models.Company
|
||||
checkResult := database.DB.Where("company_name = ?", newCompanyName).First(&existingCompany)
|
||||
if checkResult.Error == nil {
|
||||
return nil, errors.New("企业名称已存在")
|
||||
}
|
||||
}
|
||||
|
||||
err := database.DB.Transaction(func(tx *gorm.DB) error {
|
||||
if newCompanyName != companyName {
|
||||
if err := tx.Model(&models.Serial{}).Where("company_name = ?", companyName).Update("company_name", newCompanyName).Error; err != nil {
|
||||
return fmt.Errorf("更新企业赋码企业名称失败: %w", err)
|
||||
}
|
||||
if err := tx.Model(&models.EmployeeSerial{}).Where("company_name = ?", companyName).Update("company_name", newCompanyName).Error; err != nil {
|
||||
return fmt.Errorf("更新员工赋码企业名称失败: %w", err)
|
||||
}
|
||||
company.CompanyName = newCompanyName
|
||||
}
|
||||
|
||||
if isActive != nil {
|
||||
company.IsActive = *isActive
|
||||
}
|
||||
|
||||
if err := tx.Save(&company).Error; err != nil {
|
||||
return fmt.Errorf("更新企业信息失败: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.New(err.Error())
|
||||
}
|
||||
|
||||
return &company, nil
|
||||
}
|
||||
|
||||
// Delete 删除企业(同时删除关联序列号)
|
||||
func (s *CompaniesService) Delete(companyName string) error {
|
||||
var company models.Company
|
||||
if err := database.DB.Where("company_name = ?", companyName).First(&company).Error; err != nil {
|
||||
return errors.New("企业不存在")
|
||||
}
|
||||
|
||||
if err := database.DB.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Where("company_name = ?", companyName).Delete(&models.Serial{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Where("company_name = ?", companyName).Delete(&models.EmployeeSerial{}).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Delete(&company).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.New("删除企业失败")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteSerial 删除企业下指定企业赋码序列号
|
||||
func (s *CompaniesService) DeleteSerial(companyName string, serialNumber string) error {
|
||||
var serial models.Serial
|
||||
err := database.DB.Where("serial_number = ? AND company_name = ?", serialNumber, companyName).First(&serial).Error
|
||||
if err != nil {
|
||||
return errors.New("序列号不存在或不属于该企业")
|
||||
}
|
||||
|
||||
if err := database.DB.Delete(&serial).Error; err != nil {
|
||||
return errors.New("删除序列号失败")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Revoke 吊销企业(吊销所有企业赋码与员工赋码)
|
||||
func (s *CompaniesService) Revoke(companyName string) error {
|
||||
var company models.Company
|
||||
if err := database.DB.Where("company_name = ?", companyName).First(&company).Error; err != nil {
|
||||
return errors.New("企业不存在")
|
||||
}
|
||||
|
||||
if err := database.DB.Transaction(func(tx *gorm.DB) error {
|
||||
if err := tx.Model(&models.Serial{}).Where("company_name = ?", companyName).Update("is_active", false).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Model(&models.EmployeeSerial{}).Where("company_name = ?", companyName).Update("is_active", false).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Model(&company).Update("is_active", false).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.New("吊销企业失败")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetStats 获取企业统计(兼容 Node 返回结构)
|
||||
func (s *CompaniesService) GetStats() (map[string]any, error) {
|
||||
now := time.Now()
|
||||
|
||||
var companies []models.Company
|
||||
if err := database.DB.Order("updated_at DESC").Find(&companies).Error; err != nil {
|
||||
return nil, errors.New("查询企业统计失败")
|
||||
}
|
||||
|
||||
var serials []models.Serial
|
||||
if err := database.DB.Order("created_at DESC").Find(&serials).Error; err != nil {
|
||||
return nil, errors.New("查询序列号统计失败")
|
||||
}
|
||||
|
||||
var employeeSerials []models.EmployeeSerial
|
||||
if err := database.DB.Order("created_at DESC").Find(&employeeSerials).Error; err != nil {
|
||||
return nil, errors.New("查询员工序列号统计失败")
|
||||
}
|
||||
|
||||
var aftersales []models.AftersalesOrder
|
||||
if err := database.DB.Preload("Technician").Order("created_at DESC").Find(&aftersales).Error; err != nil {
|
||||
return nil, errors.New("查询售后工单统计失败")
|
||||
}
|
||||
|
||||
companyCount := len(companies)
|
||||
serialCount := len(serials)
|
||||
employeeSerialCount := len(employeeSerials)
|
||||
activeCount := 0
|
||||
for _, serial := range serials {
|
||||
if serial.IsActive && (serial.ValidUntil == nil || serial.ValidUntil.After(now)) {
|
||||
activeCount++
|
||||
}
|
||||
}
|
||||
inactiveCount := serialCount - activeCount
|
||||
|
||||
monthlyItems := make([]map[string]any, 0)
|
||||
for i := 11; i >= 0; i-- {
|
||||
date := time.Date(now.Year(), now.Month()-time.Month(i), 1, 0, 0, 0, 0, time.Local)
|
||||
monthStr := date.Format("2006-01")
|
||||
monthSerialCount := 0
|
||||
companySet := map[string]bool{}
|
||||
for _, serial := range serials {
|
||||
if serial.CreatedAt.Year() == date.Year() && serial.CreatedAt.Month() == date.Month() {
|
||||
monthSerialCount++
|
||||
companySet[serial.CompanyName] = true
|
||||
}
|
||||
}
|
||||
if monthSerialCount > 0 {
|
||||
monthlyItems = append(monthlyItems, map[string]any{
|
||||
"month": monthStr,
|
||||
"company_count": len(companySet),
|
||||
"serial_count": monthSerialCount,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
recentCompanies := make([]map[string]any, 0)
|
||||
for i, company := range companies {
|
||||
if i >= 10 {
|
||||
break
|
||||
}
|
||||
recentCompanies = append(recentCompanies, map[string]any{
|
||||
"companyName": company.CompanyName,
|
||||
"lastCreated": company.UpdatedAt,
|
||||
"status": map[bool]string{true: "active", false: "disabled"}[company.IsActive],
|
||||
})
|
||||
}
|
||||
|
||||
recentSerials := make([]map[string]any, 0)
|
||||
// 添加企业序列号
|
||||
for _, serial := range serials {
|
||||
recentSerials = append(recentSerials, map[string]any{
|
||||
"serialNumber": serial.SerialNumber,
|
||||
"companyName": serial.CompanyName,
|
||||
"isActive": serial.IsActive,
|
||||
"createdAt": serial.CreatedAt,
|
||||
"type": "company",
|
||||
})
|
||||
}
|
||||
// 添加员工序列号
|
||||
for _, serial := range employeeSerials {
|
||||
recentSerials = append(recentSerials, map[string]any{
|
||||
"serialNumber": serial.SerialNumber,
|
||||
"companyName": serial.CompanyName,
|
||||
"isActive": serial.IsActive,
|
||||
"createdAt": serial.CreatedAt,
|
||||
"type": "employee",
|
||||
"position": serial.Position,
|
||||
"employeeName": serial.EmployeeName,
|
||||
})
|
||||
}
|
||||
// 按创建时间排序,保留最新的10条
|
||||
sort.Slice(recentSerials, func(i, j int) bool {
|
||||
return recentSerials[i]["createdAt"].(time.Time).After(recentSerials[j]["createdAt"].(time.Time))
|
||||
})
|
||||
if len(recentSerials) > 10 {
|
||||
recentSerials = recentSerials[:10]
|
||||
}
|
||||
|
||||
aftersalesTotal := len(aftersales)
|
||||
aftersalesPending := 0
|
||||
aftersalesClosed := 0
|
||||
aftersalesRejected := 0
|
||||
for _, o := range aftersales {
|
||||
switch o.WorkOrderStatus {
|
||||
case "pending_confirmation":
|
||||
aftersalesPending++
|
||||
case "closed":
|
||||
aftersalesClosed++
|
||||
case "rejected":
|
||||
aftersalesRejected++
|
||||
}
|
||||
}
|
||||
|
||||
recentAftersales := make([]map[string]any, 0)
|
||||
for i, order := range aftersales {
|
||||
if i >= 10 {
|
||||
break
|
||||
}
|
||||
technicianName := ""
|
||||
if order.Technician != nil {
|
||||
technicianName = order.Technician.Name
|
||||
}
|
||||
recentAftersales = append(recentAftersales, map[string]any{
|
||||
"serialNumber": order.SerialNumber,
|
||||
"companyName": order.CompanyName,
|
||||
"serviceType": order.ServiceType,
|
||||
"workOrderStatus": order.WorkOrderStatus,
|
||||
"authorizationStatus": order.AuthorizationStatus,
|
||||
"technicianName": technicianName,
|
||||
"createdAt": order.CreatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"overview": map[string]any{
|
||||
"totalCompanies": companyCount,
|
||||
"totalSerials": serialCount,
|
||||
"totalEmployeeSerials": employeeSerialCount,
|
||||
"activeSerials": activeCount,
|
||||
"inactiveSerials": inactiveCount,
|
||||
"totalAftersales": aftersalesTotal,
|
||||
"pendingConfirmation": aftersalesPending,
|
||||
"closedAftersales": aftersalesClosed,
|
||||
"rejectedAftersales": aftersalesRejected,
|
||||
},
|
||||
"monthlyStats": monthlyItems,
|
||||
"recentCompanies": recentCompanies,
|
||||
"recentSerials": recentSerials,
|
||||
"recentAftersales": recentAftersales,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetStatsOverview 获取企业统计概览
|
||||
func (s *CompaniesService) GetStatsOverview() (*models.CompanyStatsOverviewDTO, error) {
|
||||
stats := &models.CompanyStatsOverviewDTO{}
|
||||
|
||||
if err := database.DB.Model(&models.Company{}).Count(&stats.TotalCompanies).Error; err != nil {
|
||||
return nil, errors.New("统计企业总数失败")
|
||||
}
|
||||
|
||||
if err := database.DB.Model(&models.Company{}).Where("is_active = ?", true).Count(&stats.ActiveCompanies).Error; err != nil {
|
||||
return nil, errors.New("统计启用企业数量失败")
|
||||
}
|
||||
|
||||
stats.InactiveCompanies = stats.TotalCompanies - stats.ActiveCompanies
|
||||
|
||||
if err := database.DB.Model(&models.Serial{}).Count(&stats.TotalSerials).Error; err != nil {
|
||||
return nil, errors.New("统计企业赋码总数失败")
|
||||
}
|
||||
|
||||
if err := database.DB.Model(&models.Serial{}).Where("is_active = ?", true).Count(&stats.ActiveSerials).Error; err != nil {
|
||||
return nil, errors.New("统计有效企业赋码数量失败")
|
||||
}
|
||||
|
||||
stats.RevokedSerials = stats.TotalSerials - stats.ActiveSerials
|
||||
|
||||
if err := database.DB.Model(&models.EmployeeSerial{}).Count(&stats.TotalEmployeeSerials).Error; err != nil {
|
||||
return nil, errors.New("统计员工赋码总数失败")
|
||||
}
|
||||
|
||||
if err := database.DB.Model(&models.EmployeeSerial{}).Where("is_active = ?", true).Count(&stats.ActiveEmployeeSerials).Error; err != nil {
|
||||
return nil, errors.New("统计有效员工赋码数量失败")
|
||||
}
|
||||
|
||||
stats.RevokedEmployeeSerials = stats.TotalEmployeeSerials - stats.ActiveEmployeeSerials
|
||||
|
||||
if err := database.DB.Model(&models.AftersalesOrder{}).Count(&stats.TotalAftersales).Error; err != nil {
|
||||
return nil, errors.New("统计售后工单总数失败")
|
||||
}
|
||||
if err := database.DB.Model(&models.AftersalesOrder{}).Where("work_order_status = ?", "pending_confirmation").Count(&stats.PendingConfirmation).Error; err != nil {
|
||||
return nil, errors.New("统计待客户确认工单失败")
|
||||
}
|
||||
if err := database.DB.Model(&models.AftersalesOrder{}).Where("work_order_status = ?", "closed").Count(&stats.ClosedAftersales).Error; err != nil {
|
||||
return nil, errors.New("统计已完成工单失败")
|
||||
}
|
||||
if err := database.DB.Model(&models.AftersalesOrder{}).Where("work_order_status = ?", "rejected").Count(&stats.RejectedAftersales).Error; err != nil {
|
||||
return nil, errors.New("统计已退回工单失败")
|
||||
}
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"git.beifan.cn/trace-system/backend-go/database"
|
||||
"git.beifan.cn/trace-system/backend-go/models"
|
||||
)
|
||||
|
||||
// DashboardService 控制台统计服务
|
||||
type DashboardService struct{}
|
||||
|
||||
// GetStats 获取控制台工单统计
|
||||
func (s *DashboardService) GetStats() (map[string]any, error) {
|
||||
var aftersales []models.AftersalesOrder
|
||||
if err := database.DB.Preload("Technician").Order("created_at DESC").Find(&aftersales).Error; err != nil {
|
||||
return nil, errors.New("查询售后工单统计失败")
|
||||
}
|
||||
|
||||
aftersalesTotal := len(aftersales)
|
||||
aftersalesPending := 0
|
||||
aftersalesClosed := 0
|
||||
aftersalesRejected := 0
|
||||
for _, order := range aftersales {
|
||||
switch order.WorkOrderStatus {
|
||||
case "pending_confirmation":
|
||||
aftersalesPending++
|
||||
case "closed":
|
||||
aftersalesClosed++
|
||||
case "rejected":
|
||||
aftersalesRejected++
|
||||
}
|
||||
}
|
||||
|
||||
recentAftersales := make([]map[string]any, 0)
|
||||
for i, order := range aftersales {
|
||||
if i >= 10 {
|
||||
break
|
||||
}
|
||||
technicianName := ""
|
||||
if order.Technician != nil {
|
||||
technicianName = order.Technician.Name
|
||||
}
|
||||
recentAftersales = append(recentAftersales, map[string]any{
|
||||
"serialNumber": order.SerialNumber,
|
||||
"companyName": order.CompanyName,
|
||||
"serviceType": order.ServiceType,
|
||||
"workOrderStatus": order.WorkOrderStatus,
|
||||
"authorizationStatus": order.AuthorizationStatus,
|
||||
"technicianName": technicianName,
|
||||
"createdAt": order.CreatedAt,
|
||||
})
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"overview": map[string]any{
|
||||
"totalAftersales": aftersalesTotal,
|
||||
"pendingConfirmation": aftersalesPending,
|
||||
"closedAftersales": aftersalesClosed,
|
||||
"rejectedAftersales": aftersalesRejected,
|
||||
},
|
||||
"recentAftersales": recentAftersales,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,271 +0,0 @@
|
||||
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"
|
||||
)
|
||||
|
||||
// SerialsService 序列号服务
|
||||
type SerialsService struct{}
|
||||
|
||||
// Generate 生成序列号
|
||||
func (s *SerialsService) Generate(
|
||||
companyName string,
|
||||
quantity int,
|
||||
validDays int,
|
||||
userId uint,
|
||||
prefix ...string,
|
||||
) ([]models.Serial, error) {
|
||||
var serials []models.Serial
|
||||
validUntil := time.Now().AddDate(0, 0, validDays)
|
||||
|
||||
// 检查公司是否存在,不存在则创建
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 生成序列号前缀
|
||||
var serialPrefix string
|
||||
if len(prefix) > 0 && prefix[0] != "" {
|
||||
serialPrefix = strings.ToUpper(strings.ReplaceAll(prefix[0], "[^A-Z0-9]", ""))
|
||||
} else {
|
||||
serialPrefix = fmt.Sprintf("BF%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.Serial
|
||||
checkResult := database.DB.Where("serial_number = ?", serialNumber).First(&existingSerial)
|
||||
if checkResult.Error != nil {
|
||||
serialNumbers[serialNumber] = true
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
for serialNumber := range serialNumbers {
|
||||
serial := models.Serial{
|
||||
SerialNumber: strings.ToUpper(serialNumber),
|
||||
CompanyName: companyName,
|
||||
ValidUntil: &validUntil,
|
||||
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
|
||||
}
|
||||
|
||||
// GenerateQRCode 生成二维码
|
||||
func (s *SerialsService) GenerateQRCode(
|
||||
serialNumber string,
|
||||
baseUrl string,
|
||||
requestHost string,
|
||||
protocol string,
|
||||
) (string, string, error) {
|
||||
var serial models.Serial
|
||||
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("序列号已被禁用"))
|
||||
}
|
||||
|
||||
if serial.ValidUntil != nil && serial.ValidUntil.Before(time.Now()) {
|
||||
return "", "", fmt.Errorf("序列号已过期")
|
||||
}
|
||||
|
||||
// 确定查询 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)
|
||||
}
|
||||
|
||||
// 读取文件内容
|
||||
fileContent, errRead := os.ReadFile(filePath)
|
||||
if errRead != nil {
|
||||
os.Remove(filePath)
|
||||
return "", "", fmt.Errorf("二维码文件读取失败: %w", errRead)
|
||||
}
|
||||
|
||||
// 删除临时文件
|
||||
os.Remove(filePath)
|
||||
|
||||
// 转换为 base64
|
||||
qrCodeBase64 := fmt.Sprintf("data:image/png;base64,%s", base64.StdEncoding.EncodeToString(fileContent))
|
||||
return qrCodeBase64, queryUrl, nil
|
||||
}
|
||||
|
||||
// Query 查询序列号信息
|
||||
func (s *SerialsService) Query(serialNumber string) (*models.Serial, error) {
|
||||
var serial models.Serial
|
||||
result := database.DB.Preload("User").Where("serial_number = ?", strings.ToUpper(serialNumber)).First(&serial)
|
||||
if result.Error != nil {
|
||||
return nil, fmt.Errorf("查询序列号失败: %w", errors.New("序列号不存在"))
|
||||
}
|
||||
|
||||
if serial.ValidUntil != nil && serial.ValidUntil.Before(time.Now()) {
|
||||
return nil, fmt.Errorf("序列号已过期")
|
||||
}
|
||||
|
||||
return &serial, nil
|
||||
}
|
||||
|
||||
// FindAll 获取序列号列表
|
||||
func (s *SerialsService) FindAll(page int, limit int, search string) ([]models.Serial, int, int, error) {
|
||||
var serials []models.Serial
|
||||
var total int64
|
||||
|
||||
offset := (page - 1) * limit
|
||||
db := database.DB.Preload("User")
|
||||
|
||||
// 搜索条件
|
||||
if search != "" {
|
||||
db = db.Where("serial_number LIKE ? OR company_name LIKE ?", "%"+search+"%", "%"+search+"%")
|
||||
}
|
||||
|
||||
// 获取总数
|
||||
countQuery := db.Model(&models.Serial{})
|
||||
if search != "" {
|
||||
countQuery = countQuery.Where("serial_number LIKE ? OR company_name LIKE ?", "%"+search+"%", "%"+search+"%")
|
||||
}
|
||||
countQuery.Count(&total)
|
||||
|
||||
// 分页查询
|
||||
result := db.Model(&models.Serial{}).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 *SerialsService) Update(serialNumber string, updateData models.UpdateSerialDTO) (*models.Serial, error) {
|
||||
var serial models.Serial
|
||||
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.ValidUntil != nil {
|
||||
serial.ValidUntil = updateData.ValidUntil
|
||||
}
|
||||
|
||||
if updateData.IsActive != nil {
|
||||
serial.IsActive = *updateData.IsActive
|
||||
}
|
||||
|
||||
result = database.DB.Save(&serial)
|
||||
if result.Error != nil {
|
||||
return nil, fmt.Errorf("更新序列号失败: %w", result.Error)
|
||||
}
|
||||
|
||||
_ = database.DB.Preload("User").Where("serial_number = ?", serial.SerialNumber).First(&serial)
|
||||
|
||||
return &serial, nil
|
||||
}
|
||||
|
||||
// Revoke 吊销序列号
|
||||
func (s *SerialsService) Revoke(serialNumber string) error {
|
||||
var serial models.Serial
|
||||
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
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
@@ -30,7 +29,6 @@ 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{})
|
||||
database.DB.Unscoped().Where("1 = 1").Delete(&models.AftersalesOrder{})
|
||||
|
||||
@@ -38,7 +36,6 @@ 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{})
|
||||
database.DB.Unscoped().Where("1 = 1").Delete(&models.AftersalesOrder{})
|
||||
|
||||
@@ -219,222 +216,6 @@ func TestAuthService_UpdateProfile_Success(t *testing.T) {
|
||||
database.DB.Unscoped().Delete(&user)
|
||||
}
|
||||
|
||||
func TestSerialsService_Generate_Success(t *testing.T) {
|
||||
var user models.User
|
||||
password, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
|
||||
user = models.User{
|
||||
Username: "adminuser",
|
||||
Password: string(password),
|
||||
Name: "管理员",
|
||||
Email: "admin@example.com",
|
||||
Role: "admin",
|
||||
}
|
||||
database.DB.Create(&user)
|
||||
|
||||
serialService := SerialsService{}
|
||||
serials, err := serialService.Generate("TestCompany", 5, 30, user.ID)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, serials, 5)
|
||||
assert.Equal(t, "TestCompany", serials[0].CompanyName)
|
||||
assert.True(t, serials[0].IsActive)
|
||||
|
||||
for _, serial := range serials {
|
||||
database.DB.Unscoped().Delete(&serial)
|
||||
}
|
||||
database.DB.Unscoped().Where("company_name = ?", "TestCompany").Delete(&models.Company{})
|
||||
database.DB.Unscoped().Delete(&user)
|
||||
}
|
||||
|
||||
func TestSerialsService_Generate_WithPrefix(t *testing.T) {
|
||||
var user models.User
|
||||
password, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
|
||||
user = models.User{
|
||||
Username: "adminuser2",
|
||||
Password: string(password),
|
||||
Name: "管理员2",
|
||||
Email: "admin2@example.com",
|
||||
Role: "admin",
|
||||
}
|
||||
database.DB.Create(&user)
|
||||
|
||||
serialService := SerialsService{}
|
||||
serials, err := serialService.Generate("TestCompany2", 3, 30, user.ID, "TEST")
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Len(t, serials, 3)
|
||||
assert.True(t, len(serials[0].SerialNumber) > 0)
|
||||
assert.Contains(t, serials[0].SerialNumber, "TEST")
|
||||
|
||||
for _, serial := range serials {
|
||||
database.DB.Unscoped().Delete(&serial)
|
||||
}
|
||||
database.DB.Unscoped().Where("company_name = ?", "TestCompany2").Delete(&models.Company{})
|
||||
database.DB.Unscoped().Delete(&user)
|
||||
}
|
||||
|
||||
func TestSerialsService_Query_QuerySuccess(t *testing.T) {
|
||||
var user models.User
|
||||
password, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
|
||||
user = models.User{
|
||||
Username: "adminuser3",
|
||||
Password: string(password),
|
||||
Name: "管理员3",
|
||||
Email: "admin3@example.com",
|
||||
Role: "admin",
|
||||
}
|
||||
database.DB.Create(&user)
|
||||
|
||||
serialService := SerialsService{}
|
||||
serials, _ := serialService.Generate("TestCompany3", 1, 30, user.ID, "QR")
|
||||
|
||||
serialNumber := strings.ToUpper(serials[0].SerialNumber)
|
||||
result, err := serialService.Query(serialNumber)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
assert.Equal(t, serialNumber, strings.ToUpper(result.SerialNumber))
|
||||
assert.True(t, result.IsActive)
|
||||
|
||||
for _, serial := range serials {
|
||||
database.DB.Unscoped().Delete(&serial)
|
||||
}
|
||||
database.DB.Unscoped().Where("company_name = ?", "TestCompany3").Delete(&models.Company{})
|
||||
database.DB.Unscoped().Delete(&user)
|
||||
}
|
||||
|
||||
func TestSerialsService_Query_SerialNotFound(t *testing.T) {
|
||||
serialService := SerialsService{}
|
||||
_, err := serialService.Query("NONEXISTENT")
|
||||
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestSerialsService_FindAll_Success(t *testing.T) {
|
||||
var user models.User
|
||||
password, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
|
||||
user = models.User{
|
||||
Username: "adminuser4",
|
||||
Password: string(password),
|
||||
Name: "管理员4",
|
||||
Email: "admin4@example.com",
|
||||
Role: "admin",
|
||||
}
|
||||
database.DB.Create(&user)
|
||||
|
||||
serialService := SerialsService{}
|
||||
serials, _ := serialService.Generate("TestCompany4", 10, 30, user.ID, "LIST")
|
||||
|
||||
result, total, totalPages, err := serialService.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 = ?", "TestCompany4").Delete(&models.Company{})
|
||||
database.DB.Unscoped().Delete(&user)
|
||||
}
|
||||
|
||||
func TestSerialsService_FindAll_WithSearch(t *testing.T) {
|
||||
var user models.User
|
||||
password, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
|
||||
user = models.User{
|
||||
Username: "adminuser5",
|
||||
Password: string(password),
|
||||
Name: "管理员5",
|
||||
Email: "admin5@example.com",
|
||||
Role: "admin",
|
||||
}
|
||||
database.DB.Create(&user)
|
||||
|
||||
serialService := SerialsService{}
|
||||
serials, _ := serialService.Generate("SearchCompany", 5, 30, user.ID, "SEARCH")
|
||||
|
||||
result, _, _, err := serialService.FindAll(1, 10, "SearchCompany")
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Greater(t, len(result), 0)
|
||||
assert.Equal(t, "SearchCompany", result[0].CompanyName)
|
||||
|
||||
for _, serial := range serials {
|
||||
database.DB.Unscoped().Delete(&serial)
|
||||
}
|
||||
database.DB.Unscoped().Where("company_name = ?", "SearchCompany").Delete(&models.Company{})
|
||||
database.DB.Unscoped().Delete(&user)
|
||||
}
|
||||
|
||||
func TestSerialsService_Revoke_Success(t *testing.T) {
|
||||
var user models.User
|
||||
password, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
|
||||
user = models.User{
|
||||
Username: "adminuser6",
|
||||
Password: string(password),
|
||||
Name: "管理员6",
|
||||
Email: "admin6@example.com",
|
||||
Role: "admin",
|
||||
}
|
||||
database.DB.Create(&user)
|
||||
|
||||
serialService := SerialsService{}
|
||||
serials, _ := serialService.Generate("RevokeCompany", 1, 30, user.ID, "REVOKE")
|
||||
|
||||
serialNumber := serials[0].SerialNumber
|
||||
err := serialService.Revoke(serialNumber)
|
||||
|
||||
assert.NoError(t, err)
|
||||
|
||||
var revokedSerial models.Serial
|
||||
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 = ?", "RevokeCompany").Delete(&models.Company{})
|
||||
database.DB.Unscoped().Delete(&user)
|
||||
}
|
||||
|
||||
func TestSerialsService_Update_Success(t *testing.T) {
|
||||
var user models.User
|
||||
password, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
|
||||
user = models.User{
|
||||
Username: "adminuser7",
|
||||
Password: string(password),
|
||||
Name: "管理员7",
|
||||
Email: "admin7@example.com",
|
||||
Role: "admin",
|
||||
}
|
||||
database.DB.Create(&user)
|
||||
|
||||
serialService := SerialsService{}
|
||||
serials, _ := serialService.Generate("UpdateCompany", 1, 30, user.ID, "UPDATE")
|
||||
|
||||
serialNumber := serials[0].SerialNumber
|
||||
newValidUntil := time.Now().AddDate(0, 0, 60)
|
||||
isActive := false
|
||||
|
||||
updateData := models.UpdateSerialDTO{
|
||||
ValidUntil: &newValidUntil,
|
||||
IsActive: &isActive,
|
||||
}
|
||||
|
||||
result, err := serialService.Update(serialNumber, updateData)
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, result)
|
||||
assert.False(t, result.IsActive)
|
||||
|
||||
for _, serial := range serials {
|
||||
database.DB.Unscoped().Delete(&serial)
|
||||
}
|
||||
database.DB.Unscoped().Where("company_name = ?", "UpdateCompany").Delete(&models.Company{})
|
||||
database.DB.Unscoped().Delete(&user)
|
||||
}
|
||||
|
||||
// ==================== EmployeeSerialsService 测试 ====================
|
||||
|
||||
func TestEmployeeSerialsService_Generate_Success(t *testing.T) {
|
||||
@@ -794,47 +575,3 @@ func TestEmployeeSerialsService_GenerateQRCode_Inactive(t *testing.T) {
|
||||
database.DB.Unscoped().Where("company_name = ?", "QREmpCompany2").Delete(&models.Company{})
|
||||
database.DB.Unscoped().Delete(&user)
|
||||
}
|
||||
|
||||
func TestCompaniesService_GetStatsOverview_Success(t *testing.T) {
|
||||
var user models.User
|
||||
password, _ := bcrypt.GenerateFromPassword([]byte("password123"), bcrypt.DefaultCost)
|
||||
user = models.User{
|
||||
Username: "statsadmin",
|
||||
Password: string(password),
|
||||
Name: "统计管理员",
|
||||
Email: "statsadmin@example.com",
|
||||
Role: "admin",
|
||||
}
|
||||
database.DB.Create(&user)
|
||||
|
||||
serialService := SerialsService{}
|
||||
companySerials, _ := serialService.Generate("StatsCompany", 2, 30, user.ID, "STAT")
|
||||
|
||||
employeeSerialsService := EmployeeSerialsService{}
|
||||
employeeSerials, _ := employeeSerialsService.Generate("StatsCompany", "技术部", "测试员工", 2, user.ID, "")
|
||||
|
||||
_ = serialService.Revoke(companySerials[0].SerialNumber)
|
||||
_ = employeeSerialsService.Revoke(employeeSerials[0].SerialNumber)
|
||||
|
||||
companiesService := CompaniesService{}
|
||||
stats, err := companiesService.GetStatsOverview()
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, stats)
|
||||
assert.GreaterOrEqual(t, stats.TotalCompanies, int64(1))
|
||||
assert.GreaterOrEqual(t, stats.TotalSerials, int64(2))
|
||||
assert.GreaterOrEqual(t, stats.TotalEmployeeSerials, int64(2))
|
||||
assert.GreaterOrEqual(t, stats.RevokedSerials, int64(1))
|
||||
assert.GreaterOrEqual(t, stats.RevokedEmployeeSerials, int64(1))
|
||||
|
||||
for _, serial := range companySerials {
|
||||
database.DB.Unscoped().Delete(&serial)
|
||||
}
|
||||
|
||||
for _, serial := range employeeSerials {
|
||||
database.DB.Unscoped().Delete(&serial)
|
||||
}
|
||||
|
||||
database.DB.Unscoped().Where("company_name = ?", "StatsCompany").Delete(&models.Company{})
|
||||
database.DB.Unscoped().Delete(&user)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user