feat: update aftersales service types and serial format

This commit is contained in:
Frudrax Cheng
2026-05-28 09:12:01 +08:00
parent 6a48b0624f
commit da02be0a4f
3 changed files with 16 additions and 15 deletions
+2 -2
View File
@@ -272,7 +272,7 @@ type CreateAftersalesOrderDTO struct {
CompanyAddress string `json:"companyAddress" validate:"required"` CompanyAddress string `json:"companyAddress" validate:"required"`
ContactName string `json:"contactName" validate:"required"` ContactName string `json:"contactName" validate:"required"`
ContactPhone string `json:"contactPhone" validate:"required,len=11"` ContactPhone string `json:"contactPhone" validate:"required,len=11"`
ServiceType string `json:"serviceType" validate:"required,oneof=software hardware other"` ServiceType string `json:"serviceType" validate:"required,oneof=software hardware maintenance"`
IssueDescription string `json:"issueDescription" validate:"required"` IssueDescription string `json:"issueDescription" validate:"required"`
TechnicianID *uint `json:"technicianId,omitempty"` TechnicianID *uint `json:"technicianId,omitempty"`
} }
@@ -282,7 +282,7 @@ type UpdateAftersalesOrderDTO struct {
CompanyAddress string `json:"companyAddress,omitempty"` CompanyAddress string `json:"companyAddress,omitempty"`
ContactName string `json:"contactName,omitempty"` ContactName string `json:"contactName,omitempty"`
ContactPhone string `json:"contactPhone,omitempty" validate:"omitempty,len=11"` ContactPhone string `json:"contactPhone,omitempty" validate:"omitempty,len=11"`
ServiceType string `json:"serviceType,omitempty" validate:"omitempty,oneof=software hardware other"` ServiceType string `json:"serviceType,omitempty" validate:"omitempty,oneof=software hardware maintenance"`
IssueDescription string `json:"issueDescription,omitempty"` IssueDescription string `json:"issueDescription,omitempty"`
ResolutionNote string `json:"resolutionNote,omitempty"` ResolutionNote string `json:"resolutionNote,omitempty"`
TechnicianID *uint `json:"technicianId,omitempty"` TechnicianID *uint `json:"technicianId,omitempty"`
+8 -7
View File
@@ -108,26 +108,27 @@ func normalizeAftersalesSerial(sn string) string {
} }
// generateUniqueSerial 生成唯一的售后工单序列号 // generateUniqueSerial 生成唯一的售后工单序列号
// 格式:zjbf-sh-YYMMNNYY=年份后两位,MM=月份,NN=当第几单(至少 2 位,溢出自然加宽) // 格式:zjbf-sh-YYMMDDNNYY=年份后两位,MM=月份,DD=日期,NN=当第几单(至少 2 位,溢出自然加宽)
func (s *AftersalesService) generateUniqueSerial() (string, error) { func (s *AftersalesService) generateUniqueSerial() (string, error) {
now := time.Now() now := time.Now()
yy := now.Year() % 100 yy := now.Year() % 100
mm := int(now.Month()) mm := int(now.Month())
dd := now.Day()
monthStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()) dayStart := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location())
nextMonth := monthStart.AddDate(0, 1, 0) nextDay := dayStart.AddDate(0, 0, 1)
// 统计本月已创建工单数(含软删除,避免编号回收) // 统计当天已创建工单数(含软删除,避免编号回收)
var count int64 var count int64
if err := database.DB.Unscoped().Model(&models.AftersalesOrder{}). if err := database.DB.Unscoped().Model(&models.AftersalesOrder{}).
Where("created_at >= ? AND created_at < ?", monthStart, nextMonth). Where("created_at >= ? AND created_at < ?", dayStart, nextDay).
Count(&count).Error; err != nil { Count(&count).Error; err != nil {
return "", fmt.Errorf("统计当工单数失败: %w", err) return "", fmt.Errorf("统计当工单数失败: %w", err)
} }
seq := int(count) + 1 seq := int(count) + 1
for attempt := 0; attempt < 100; attempt++ { for attempt := 0; attempt < 100; attempt++ {
candidate := fmt.Sprintf("%s%02d%02d%02d", aftersalesSerialPrefix, yy, mm, seq) candidate := fmt.Sprintf("%s%02d%02d%02d%02d", aftersalesSerialPrefix, yy, mm, dd, seq)
var existing models.AftersalesOrder var existing models.AftersalesOrder
result := database.DB.Unscoped().Where("serial_number = ?", candidate).First(&existing) result := database.DB.Unscoped().Where("serial_number = ?", candidate).First(&existing)
+6 -6
View File
@@ -55,7 +55,7 @@ func createOrderFor(t *testing.T, user models.User, phone string) *models.Afters
return order return order
} }
func TestAftersalesService_Create_GeneratesYYMMNNSerial(t *testing.T) { func TestAftersalesService_Create_GeneratesYYMMDDNNSerial(t *testing.T) {
user := seedTechnician(t, "aftersales_create_tech") user := seedTechnician(t, "aftersales_create_tech")
defer database.DB.Unscoped().Delete(&user) defer database.DB.Unscoped().Delete(&user)
@@ -72,7 +72,7 @@ func TestAftersalesService_Create_GeneratesYYMMNNSerial(t *testing.T) {
assert.NoError(t, err) assert.NoError(t, err)
assert.NotNil(t, order) assert.NotNil(t, order)
assert.True(t, strings.HasPrefix(order.SerialNumber, "zjbf-sh-")) assert.True(t, strings.HasPrefix(order.SerialNumber, "zjbf-sh-"))
assert.Len(t, order.SerialNumber, len("zjbf-sh-")+6, "default serial should be 6 digits (YYMMNN)") assert.Len(t, order.SerialNumber, len("zjbf-sh-")+8, "default serial should be 8 digits (YYMMDDNN)")
assert.Equal(t, WorkOrderStatusCreated, order.WorkOrderStatus) assert.Equal(t, WorkOrderStatusCreated, order.WorkOrderStatus)
assert.Equal(t, AuthorizationStatusPending, order.AuthorizationStatus) assert.Equal(t, AuthorizationStatusPending, order.AuthorizationStatus)
assert.NotNil(t, order.TechnicianID) assert.NotNil(t, order.TechnicianID)
@@ -82,25 +82,25 @@ func TestAftersalesService_Create_GeneratesYYMMNNSerial(t *testing.T) {
database.DB.Unscoped().Where("company_name = ?", "AftersalesSerialCo").Delete(&models.Company{}) database.DB.Unscoped().Where("company_name = ?", "AftersalesSerialCo").Delete(&models.Company{})
} }
func TestAftersalesService_Create_SerialIncrementsWithinMonth(t *testing.T) { func TestAftersalesService_Create_SerialIncrementsWithinDay(t *testing.T) {
user := seedTechnician(t, "aftersales_seq_tech") user := seedTechnician(t, "aftersales_seq_tech")
defer database.DB.Unscoped().Delete(&user) defer database.DB.Unscoped().Delete(&user)
svc := AftersalesService{} svc := AftersalesService{}
first, err := svc.Create(models.CreateAftersalesOrderDTO{ first, err := svc.Create(models.CreateAftersalesOrderDTO{
CompanyName: "SeqCo", CompanyAddress: "addr", ContactName: "A", ContactPhone: "13800002000", CompanyName: "SeqCo", CompanyAddress: "addr", ContactName: "A", ContactPhone: "13800002000",
ServiceType: "other", IssueDescription: "issue 1", ServiceType: "maintenance", IssueDescription: "issue 1",
}, user.ID) }, user.ID)
assert.NoError(t, err) assert.NoError(t, err)
second, err := svc.Create(models.CreateAftersalesOrderDTO{ second, err := svc.Create(models.CreateAftersalesOrderDTO{
CompanyName: "SeqCo", CompanyAddress: "addr", ContactName: "A", ContactPhone: "13800002000", CompanyName: "SeqCo", CompanyAddress: "addr", ContactName: "A", ContactPhone: "13800002000",
ServiceType: "other", IssueDescription: "issue 2", ServiceType: "maintenance", IssueDescription: "issue 2",
}, user.ID) }, user.ID)
assert.NoError(t, err) assert.NoError(t, err)
assert.NotEqual(t, first.SerialNumber, second.SerialNumber) assert.NotEqual(t, first.SerialNumber, second.SerialNumber)
// 第二单的序号应大于第一单(按递增) // 第二单的序号应大于第一单(按递增)
assert.True(t, second.SerialNumber > first.SerialNumber) assert.True(t, second.SerialNumber > first.SerialNumber)
database.DB.Unscoped().Delete(first) database.DB.Unscoped().Delete(first)