feat: record ticket assignment progress
This commit is contained in:
@@ -64,6 +64,32 @@ func normalizeTicketStaleHours(staleHours int) int {
|
||||
}
|
||||
}
|
||||
|
||||
func buildTicketAssignmentProgressContent(fromUser *models.User, toUser *models.User, reason string) string {
|
||||
fromName := ticketAssignmentUserDisplayName(fromUser)
|
||||
if fromName == "" {
|
||||
fromName = "未分配"
|
||||
}
|
||||
toName := ticketAssignmentUserDisplayName(toUser)
|
||||
if toName == "" && toUser != nil {
|
||||
toName = toUser.Username
|
||||
}
|
||||
content := "指派处理人:" + fromName + " -> " + toName
|
||||
if trimmedReason := strings.TrimSpace(reason); trimmedReason != "" {
|
||||
content += ",原因:" + trimmedReason
|
||||
}
|
||||
return content
|
||||
}
|
||||
|
||||
func ticketAssignmentUserDisplayName(user *models.User) string {
|
||||
if user == nil {
|
||||
return ""
|
||||
}
|
||||
if strings.TrimSpace(user.Nickname) != "" {
|
||||
return strings.TrimSpace(user.Nickname)
|
||||
}
|
||||
return strings.TrimSpace(user.Username)
|
||||
}
|
||||
|
||||
func (s *ticketService) Get(id int64) *models.Ticket {
|
||||
return repositories.TicketRepository.Get(sqls.DB(), id)
|
||||
}
|
||||
@@ -475,6 +501,14 @@ func (s *ticketService) assignTicketTx(tx *gorm.DB, req request.AssignTicketRequ
|
||||
if err := s.validateRequiredAssignee(req.ToUserID); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
toUser := repositories.UserRepository.Get(tx, req.ToUserID)
|
||||
if toUser == nil || toUser.Status != enums.StatusOk {
|
||||
return nil, errorsx.InvalidParam("负责人不存在")
|
||||
}
|
||||
var fromUser *models.User
|
||||
if ticket.CurrentAssigneeID > 0 {
|
||||
fromUser = repositories.UserRepository.Get(tx, ticket.CurrentAssigneeID)
|
||||
}
|
||||
now := time.Now()
|
||||
if err := repositories.TicketRepository.Updates(tx, ticket.ID, map[string]any{
|
||||
"current_assignee_id": req.ToUserID,
|
||||
@@ -484,6 +518,14 @@ func (s *ticketService) assignTicketTx(tx *gorm.DB, req request.AssignTicketRequ
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := repositories.TicketProgressRepository.Create(tx, &models.TicketProgress{
|
||||
TicketID: ticket.ID,
|
||||
Content: buildTicketAssignmentProgressContent(fromUser, toUser, req.Reason),
|
||||
AuthorID: operator.UserID,
|
||||
CreatedAt: now,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &events.TicketAssignedEvent{
|
||||
TicketID: ticket.ID,
|
||||
FromUserID: ticket.CurrentAssigneeID,
|
||||
|
||||
@@ -265,6 +265,47 @@ func TestTicketServiceAssignTicketRejectsDisabledUser(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTicketServiceAssignTicketCreatesProgressEntry(t *testing.T) {
|
||||
setupTicketTestDB(t)
|
||||
operator := createTestOperator(t, "assign-progress-operator")
|
||||
firstAssignee := createTestOperator(t, "assign-progress-first")
|
||||
nextAssignee := createTestOperator(t, "assign-progress-next")
|
||||
ticket, err := services.TicketService.CreateTicket(request.CreateTicketRequest{
|
||||
Title: "assign progress ticket",
|
||||
Description: "assign progress description",
|
||||
CurrentAssigneeID: firstAssignee.UserID,
|
||||
}, operator)
|
||||
if err != nil {
|
||||
t.Fatalf("CreateTicket() error = %v", err)
|
||||
}
|
||||
|
||||
if err := services.TicketService.AssignTicket(request.AssignTicketRequest{
|
||||
TicketID: ticket.ID,
|
||||
ToUserID: nextAssignee.UserID,
|
||||
Reason: "需要二线继续跟进",
|
||||
}, operator); err != nil {
|
||||
t.Fatalf("AssignTicket() error = %v", err)
|
||||
}
|
||||
|
||||
progresses := services.TicketProgressService.Find(sqls.NewCnd().Eq("ticket_id", ticket.ID).Asc("id"))
|
||||
if len(progresses) != 2 {
|
||||
t.Fatalf("expected create progress and assignment progress, got %d: %+v", len(progresses), progresses)
|
||||
}
|
||||
assignmentProgress := progresses[1]
|
||||
if assignmentProgress.AuthorID != operator.UserID {
|
||||
t.Fatalf("expected assignment progress author %d, got %d", operator.UserID, assignmentProgress.AuthorID)
|
||||
}
|
||||
if !strings.Contains(assignmentProgress.Content, "指派处理人") {
|
||||
t.Fatalf("expected assignment progress content to mention assignment, got %q", assignmentProgress.Content)
|
||||
}
|
||||
if !strings.Contains(assignmentProgress.Content, firstAssignee.Username) || !strings.Contains(assignmentProgress.Content, nextAssignee.Username) {
|
||||
t.Fatalf("expected assignment progress to include assignee names, got %q", assignmentProgress.Content)
|
||||
}
|
||||
if !strings.Contains(assignmentProgress.Content, "需要二线继续跟进") {
|
||||
t.Fatalf("expected assignment reason in progress content, got %q", assignmentProgress.Content)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTicketServiceCreateTicketRejectsMismatchedCustomerConversation(t *testing.T) {
|
||||
setupTicketTestDB(t)
|
||||
operator := createTestOperator(t, "mismatch-operator")
|
||||
|
||||
Reference in New Issue
Block a user