package services import ( "strings" "testing" "github.com/stretchr/testify/assert" "git.beifan.cn/trace-system/backend-go/database" "git.beifan.cn/trace-system/backend-go/models" ) func seedTechnician(t *testing.T, username string) models.User { t.Helper() user := models.User{ Username: username, Password: "hashed", Name: "技术员-" + username, Email: username + "@example.com", Role: "technician", } if err := database.DB.Create(&user).Error; err != nil { t.Fatalf("seed technician failed: %v", err) } return user } func createOrderFor(t *testing.T, user models.User, phone string) *models.AftersalesOrder { t.Helper() svc := AftersalesService{} order, err := svc.Create(models.CreateAftersalesOrderDTO{ CompanyName: "TestCo_" + user.Username, CompanyAddress: "测试地址", ContactName: "张三", ContactPhone: phone, ServiceType: "software", IssueDescription: "系统无法启动", }, user.ID) if err != nil { t.Fatalf("create order failed: %v", err) } return order } func TestAftersalesService_Create_GeneratesYYMMNNSerial(t *testing.T) { user := seedTechnician(t, "aftersales_create_tech") defer database.DB.Unscoped().Delete(&user) svc := AftersalesService{} order, err := svc.Create(models.CreateAftersalesOrderDTO{ CompanyName: "AftersalesSerialCo", CompanyAddress: "杭州市西湖区", ContactName: "李四", ContactPhone: "13800001234", ServiceType: "hardware", IssueDescription: "硬盘故障", }, user.ID) assert.NoError(t, err) assert.NotNil(t, order) 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.Equal(t, WorkOrderStatusCreated, order.WorkOrderStatus) assert.Equal(t, AuthorizationStatusPending, order.AuthorizationStatus) assert.NotNil(t, order.TechnicianID) assert.Equal(t, user.ID, *order.TechnicianID) database.DB.Unscoped().Delete(order) database.DB.Unscoped().Where("company_name = ?", "AftersalesSerialCo").Delete(&models.Company{}) } func TestAftersalesService_Create_SerialIncrementsWithinMonth(t *testing.T) { user := seedTechnician(t, "aftersales_seq_tech") defer database.DB.Unscoped().Delete(&user) svc := AftersalesService{} first, err := svc.Create(models.CreateAftersalesOrderDTO{ CompanyName: "SeqCo", CompanyAddress: "addr", ContactName: "A", ContactPhone: "13800002000", ServiceType: "other", IssueDescription: "issue 1", }, user.ID) assert.NoError(t, err) second, err := svc.Create(models.CreateAftersalesOrderDTO{ CompanyName: "SeqCo", CompanyAddress: "addr", ContactName: "A", ContactPhone: "13800002000", ServiceType: "other", IssueDescription: "issue 2", }, user.ID) assert.NoError(t, err) assert.NotEqual(t, first.SerialNumber, second.SerialNumber) // 第二单的序号应大于第一单(按月递增) assert.True(t, second.SerialNumber > first.SerialNumber) database.DB.Unscoped().Delete(first) database.DB.Unscoped().Delete(second) database.DB.Unscoped().Where("company_name = ?", "SeqCo").Delete(&models.Company{}) } func TestAftersalesService_SubmitForConfirmation_OwnerOnly(t *testing.T) { owner := seedTechnician(t, "aftersales_owner") intruder := seedTechnician(t, "aftersales_intruder") defer database.DB.Unscoped().Delete(&owner) defer database.DB.Unscoped().Delete(&intruder) order := createOrderFor(t, owner, "13800003000") defer database.DB.Unscoped().Delete(order) defer database.DB.Unscoped().Where("company_name = ?", order.CompanyName).Delete(&models.Company{}) svc := AftersalesService{} // 非负责人技术员不能提交 _, err := svc.SubmitForConfirmation(order.SerialNumber, models.SubmitForConfirmationDTO{ ResolutionNote: "已重装系统", }, intruder) assert.Error(t, err) // 负责人可以提交 updated, err := svc.SubmitForConfirmation(order.SerialNumber, models.SubmitForConfirmationDTO{ ResolutionNote: "已重装系统", }, owner) assert.NoError(t, err) assert.Equal(t, WorkOrderStatusPendingConfirmation, updated.WorkOrderStatus) assert.Equal(t, "已重装系统", updated.ResolutionNote) } func TestAftersalesService_SubmitForConfirmation_RejectsClosed(t *testing.T) { owner := seedTechnician(t, "aftersales_closed_owner") defer database.DB.Unscoped().Delete(&owner) order := createOrderFor(t, owner, "13800004000") defer database.DB.Unscoped().Delete(order) defer database.DB.Unscoped().Where("company_name = ?", order.CompanyName).Delete(&models.Company{}) svc := AftersalesService{} _, err := svc.SubmitForConfirmation(order.SerialNumber, models.SubmitForConfirmationDTO{ ResolutionNote: "first submit", }, owner) assert.NoError(t, err) // 已经 pending_confirmation 状态,不能再提交 _, err = svc.SubmitForConfirmation(order.SerialNumber, models.SubmitForConfirmationDTO{ ResolutionNote: "second submit", }, owner) assert.Error(t, err) } func TestAftersalesService_PublicQuery_MasksPhoneAndSetsScannedAt(t *testing.T) { owner := seedTechnician(t, "aftersales_public_owner") defer database.DB.Unscoped().Delete(&owner) order := createOrderFor(t, owner, "13800005000") defer database.DB.Unscoped().Delete(order) defer database.DB.Unscoped().Where("company_name = ?", order.CompanyName).Delete(&models.Company{}) svc := AftersalesService{} view, err := svc.PublicQuery(order.SerialNumber) assert.NoError(t, err) assert.NotNil(t, view) assert.Equal(t, order.CompanyName, view.CompanyName) assert.Equal(t, order.ContactName, view.ContactName) // PublicView 不应该带电话字段(只有以下字段,通过类型保证) // 验证 ScannedAt 已设置 var refreshed models.AftersalesOrder database.DB.Where("serial_number = ?", order.SerialNumber).First(&refreshed) assert.NotNil(t, refreshed.ScannedAt) } func TestAftersalesService_CustomerConfirm_Authorize(t *testing.T) { owner := seedTechnician(t, "aftersales_auth_owner") defer database.DB.Unscoped().Delete(&owner) order := createOrderFor(t, owner, "13800006789") defer database.DB.Unscoped().Delete(order) defer database.DB.Unscoped().Where("company_name = ?", order.CompanyName).Delete(&models.Company{}) svc := AftersalesService{} _, _ = svc.SubmitForConfirmation(order.SerialNumber, models.SubmitForConfirmationDTO{ ResolutionNote: "done", }, owner) view, err := svc.CustomerConfirm(order.SerialNumber, models.CustomerConfirmDTO{ PhoneLast4: "6789", Action: "authorize", }) assert.NoError(t, err) assert.NotNil(t, view) assert.Equal(t, WorkOrderStatusClosed, view.WorkOrderStatus) assert.Equal(t, AuthorizationStatusAuthorized, view.AuthorizationStatus) assert.NotNil(t, view.ConfirmedAt) } func TestAftersalesService_CustomerConfirm_PhoneMismatch(t *testing.T) { owner := seedTechnician(t, "aftersales_phone_owner") defer database.DB.Unscoped().Delete(&owner) order := createOrderFor(t, owner, "13800007777") defer database.DB.Unscoped().Delete(order) defer database.DB.Unscoped().Where("company_name = ?", order.CompanyName).Delete(&models.Company{}) svc := AftersalesService{} _, _ = svc.SubmitForConfirmation(order.SerialNumber, models.SubmitForConfirmationDTO{ ResolutionNote: "done", }, owner) _, err := svc.CustomerConfirm(order.SerialNumber, models.CustomerConfirmDTO{ PhoneLast4: "0000", Action: "authorize", }) assert.Error(t, err) assert.Contains(t, err.Error(), "手机号校验失败") } func TestAftersalesService_CustomerConfirm_RejectIncrementsCount(t *testing.T) { owner := seedTechnician(t, "aftersales_reject_owner") defer database.DB.Unscoped().Delete(&owner) order := createOrderFor(t, owner, "13800008888") defer database.DB.Unscoped().Delete(order) defer database.DB.Unscoped().Where("company_name = ?", order.CompanyName).Delete(&models.Company{}) svc := AftersalesService{} _, _ = svc.SubmitForConfirmation(order.SerialNumber, models.SubmitForConfirmationDTO{ ResolutionNote: "done", }, owner) view, err := svc.CustomerConfirm(order.SerialNumber, models.CustomerConfirmDTO{ PhoneLast4: "8888", Action: "reject", RejectReason: "没修好", }) assert.NoError(t, err) assert.Equal(t, WorkOrderStatusRejected, view.WorkOrderStatus) assert.Equal(t, AuthorizationStatusUnauthorized, view.AuthorizationStatus) var refreshed models.AftersalesOrder database.DB.Where("serial_number = ?", order.SerialNumber).First(&refreshed) assert.Equal(t, 1, refreshed.RejectCount) assert.Contains(t, refreshed.ResolutionNote, "没修好") // 退回后技术员可以再次提交 _, err = svc.SubmitForConfirmation(order.SerialNumber, models.SubmitForConfirmationDTO{ ResolutionNote: "re-do", }, owner) assert.NoError(t, err) } func TestAftersalesService_CustomerConfirm_RejectsWrongStatus(t *testing.T) { owner := seedTechnician(t, "aftersales_wrongstatus_owner") defer database.DB.Unscoped().Delete(&owner) order := createOrderFor(t, owner, "13800009999") defer database.DB.Unscoped().Delete(order) defer database.DB.Unscoped().Where("company_name = ?", order.CompanyName).Delete(&models.Company{}) svc := AftersalesService{} // 未提交客户确认,工单仍是 created,应该拒绝 _, err := svc.CustomerConfirm(order.SerialNumber, models.CustomerConfirmDTO{ PhoneLast4: "9999", Action: "authorize", }) assert.Error(t, err) } func TestAftersalesService_ForceClose_AdminOverride(t *testing.T) { owner := seedTechnician(t, "aftersales_force_owner") defer database.DB.Unscoped().Delete(&owner) order := createOrderFor(t, owner, "13800001111") defer database.DB.Unscoped().Delete(order) defer database.DB.Unscoped().Where("company_name = ?", order.CompanyName).Delete(&models.Company{}) svc := AftersalesService{} updated, err := svc.ForceClose(order.SerialNumber) assert.NoError(t, err) assert.Equal(t, WorkOrderStatusClosed, updated.WorkOrderStatus) assert.Equal(t, AuthorizationStatusAuthorized, updated.AuthorizationStatus) }