package mf_callback_ticket import ( "fmt" "time" "be.ems/lib/dborm" "be.ems/lib/email" "gorm.io/gorm" ) type CallbackTicketService struct { db *gorm.DB } // 构造函数示例 func NewCallbackTicketService() *CallbackTicketService { db := dborm.DefaultDB() return &CallbackTicketService{db: db} } // SelectCallbackTicket 根据条件分页查询回调工单 func (s *CallbackTicketService) SelectCallbackTicket(query CallbackTicketQuery) ([]CallbackTicket, int, error) { var tickets []CallbackTicket var total int64 db := s.db.Table("mf_callback_ticket") if query.CallerNumber != "" { db = db.Where("caller_number = ?", query.CallerNumber) } if query.AgentName != "" { db = db.Where("agent_name = ?", query.AgentName) } if query.Status != "" { db = db.Where("status = ?", query.Status) } if query.StartTime != "" && query.EndTime != "" { db = db.Where("created_at BETWEEN ? AND ?", query.StartTime, query.EndTime) } else if query.StartTime != "" { db = db.Where("created_at >= ?", query.StartTime) } else if query.EndTime != "" { db = db.Where("created_at <= ?", query.EndTime) } // 统计总数 if err := db.Count(&total).Error; err != nil { return nil, 0, fmt.Errorf("failed to count callback tickets: %w", err) } // 分页查询 offset := (query.PageNum - 1) * query.PageSize if err := db.Limit(query.PageSize).Offset(offset).Order("created_at desc").Find(&tickets).Error; err != nil { return nil, 0, fmt.Errorf("failed to select callback tickets: %w", err) } return tickets, int(total), nil } // SelectCallbackTicketByPage 分页查询回调工单 // @Description 分页查询回调工单 // @param page 页码 // @param pageSize 每页大小 // @return []MfCallbackTicket 回调工单列表 // @return int 总记录数 // @return error 错误信息 // @example // mfCallbackTicketService.SelectCallbackTicketByPage(1, 10) func (s *CallbackTicketService) SelectCallbackTicketByPage(pageNum int, pageSize int) ([]CallbackTicket, int, error) { var tickets []CallbackTicket var total int64 // 统计总数 if err := s.db.Table("mf_callback_ticket").Count(&total).Error; err != nil { return nil, 0, fmt.Errorf("failed to count callback tickets: %w", err) } // 分页查询 offset := (pageNum - 1) * pageSize if err := s.db.Table("mf_callback_ticket"). Limit(pageSize). Offset(offset). Find(&tickets).Error; err != nil { return nil, 0, fmt.Errorf("failed to select callback tickets: %w", err) } return tickets, int(total), nil } func (s *CallbackTicketService) InsertCallbackTicket(ticket CallbackTicket) error { // 判断主叫号码是否已存在未处理完的工单 var existingCount int64 if err := s.db.Table("mf_callback_ticket"). Where("caller_number = ? AND status IN ('NEW', 'PENDING', 'IN_PROGRESS')", ticket.CallerNumber). Count(&existingCount).Error; err != nil { return fmt.Errorf("failed to check existing tickets: %w", err) } // 如果存在未处理完的工单,返回错误 if existingCount > 0 { return fmt.Errorf("caller %s already has a pending ticket", ticket.CallerNumber) } // 这里可以使用ORM或其他方式将ticket插入到数据库中 if err := s.db.Table("mf_callback_ticket").Create(&ticket).Error; err != nil { return fmt.Errorf("failed to insert callback ticket: %w", err) } return nil } // SelectCallbackTicketById 根据工单ID查询回调工单 // @Description 根据工单ID查询回调工单 // @param ticketId 工单ID // @return *CallbackTicket 回调工单对象 // @return error 错误信息 // @example // mfCallbackTicketService.SelectCallbackTicketById(12345) func (s *CallbackTicketService) SelectCallbackTicketById(ticketId int64) (*CallbackTicket, error) { var ticket CallbackTicket if err := s.db.Table("mf_callback_ticket"). Where("ticket_id = ?", ticketId). First(&ticket).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, fmt.Errorf("callback ticket with ID %d not found", ticketId) } return nil, fmt.Errorf("failed to select callback ticket: %w", err) } return &ticket, nil } // UpdateCallbackTicket 更新回调工单 // @Description 更新回调工单 // @param ticket 回调工单对象 // @return error 错误信息 // @example // mfCallbackTicketService.UpdateCallbackTicket(ticket) func (s *CallbackTicketService) UpdateCallbackTicket(ticket CallbackTicket) error { if err := s.db.Table("mf_callback_ticket"). Where("ticket_id = ?", ticket.TicketId). Updates(ticket).Error; err != nil { return fmt.Errorf("failed to update callback ticket: %w", err) } return nil } // DeleteCallbackTicket 删除回调工单 // @Description 删除回调工单 // @param ticketId 工单ID // @return error 错误信息 // @example // mfCallbackTicketService.DeleteCallbackTicket(12345) func (s *CallbackTicketService) DeleteCallbackTicket(ticketId int64) error { if err := s.db.Table("mf_callback_ticket"). Where("ticket_id = ?", ticketId). Delete(&CallbackTicket{}).Error; err != nil { return fmt.Errorf("failed to delete callback ticket: %w", err) } return nil } // GetLastAssignedAgent 获取最近一个分配的座席名称 // @Description 获取最近分配工单的座席名称 // @return string 座席名称 // @return error 错误信息 func (s *CallbackTicketService) GetLastAssignedAgent() (string, error) { var lastTicket CallbackTicket if err := s.db.Table("mf_callback_ticket"). Where("agent_name <> ''"). Order("created_at DESC"). First(&lastTicket).Error; err != nil { if err == gorm.ErrRecordNotFound { // 没有记录属于正常情况,返回空字符串 return "", nil } return "", fmt.Errorf("failed to query last ticket: %w", err) } return lastTicket.AgentName, nil } // SelectNextAgent 根据上一个座席和座席列表选择下一个座席 // @Description 选择下一个要分配的座席 // @param agents 座席列表 // @param lastAgentName 上一个座席名称 // @return string 下一个座席名称 func (s *CallbackTicketService) SelectNextAgent(agents []AgentInfo, lastAgentName string) string { if len(agents) == 0 { return "" } // 默认选第一个座席 selectedAgent := agents[0].Name // 如果没有上一个座席,直接返回第一个 if lastAgentName == "" { return selectedAgent } // 找到上一个座席的下一个 foundLastAgent := false for i, agent := range agents { if foundLastAgent { // 找到上一个座席的下一个 return agent.Name } if agent.Name == lastAgentName { foundLastAgent = true // 如果是最后一个座席,则循环回第一个 if i == len(agents)-1 { return agents[0].Name } } } // 如果没找到上一个座席(可能被删除了),使用第一个座席 return selectedAgent } // FindCallbackTicketByAgentAndCaller 通过座席号码和主叫号码查找符合条件的工单 // @Description 通过座席号码和主叫号码查找符合条件的工单 // @param agentName 座席号码 // @param callerNumber 主叫号码 // @return *CallbackTicket 回调工单对象 // @return error 错误信息 func (s *CallbackTicketService) FindCallbackTicketByAgentAndCaller(agentName string, callerNumber string) (*CallbackTicket, error) { var ticket CallbackTicket if err := s.db.Table("mf_callback_ticket"). Where("agent_name = ? AND caller_number = ? AND status IN ('NEW', 'PENDING', 'IN_PROGRESS', 'NO_ANSWER_1', 'NO_ANSWER_2')", agentName, callerNumber). Order("created_at DESC"). First(&ticket).Error; err != nil { if err == gorm.ErrRecordNotFound { return nil, nil // 没有找到记录,返回nil, nil } return nil, fmt.Errorf("failed to find callback ticket: %w", err) } return &ticket, nil } // ProcessCallbackResult 处理回拨结果并更新工单 // @Description 处理回拨结果并更新工单状态 // @param ticket 要更新的工单 // @param callDuration 通话时长 // @param answerTime 应答时间 // @param releaseTime 释放时间 // @param cause 释放原因 // @return error 错误信息 func (s *CallbackTicketService) ProcessCallbackResult(ticket *CallbackTicket, callDuration float64, answerTime string, releaseTime string, cause string) error { if ticket == nil { return fmt.Errorf("ticket cannot be nil") } // 构建更新信息 now := time.Now().UnixMicro() updatedTicket := CallbackTicket{ TicketId: ticket.TicketId, UpdatedAt: &now, } // 构建评论内容 comment := fmt.Sprintf("回拨时间: %s, 释放时间: %s, 原因: %s", answerTime, releaseTime, cause) // 根据通话时长判断处理结果 if callDuration > 0 { // 通话已接通,标记为已处理完毕 updatedTicket.Status = TicketStatusClosed.Enum() updatedTicket.Comment = fmt.Sprintf("%s, 通话时长: %.1f秒, 状态: 已接通并完成", comment, callDuration) } else { // 通话未接通,根据当前状态修改为相应状态 switch ticket.Status { case TicketStatusNew.Enum(), TicketStatusInProgress.Enum(): // 第一次未接通 updatedTicket.Status = TicketStatusNoAnswer1.Enum() updatedTicket.Comment = fmt.Sprintf("%s, 通话时长: %.1f秒, 状态: 第一次未接通", comment, callDuration) case TicketStatusNoAnswer1.Enum(): // 第二次未接通 updatedTicket.Status = TicketStatusNoAnswer2.Enum() updatedTicket.Comment = fmt.Sprintf("%s, 通话时长: %.1f秒, 状态: 第二次未接通", comment, callDuration) case TicketStatusNoAnswer2.Enum(): // 第三次未接通,挂起 updatedTicket.Status = TicketStatusPending.Enum() updatedTicket.Comment = fmt.Sprintf("%s, 通话时长: %.1f秒, 状态: 多次未接通,工单挂起", comment, callDuration) default: // 其他状态保持不变 updatedTicket.Status = ticket.Status updatedTicket.Comment = fmt.Sprintf("%s, 通话时长: %.1f秒, 状态: 未知状态", comment, callDuration) } } // 更新工单 if err := s.db.Table("mf_callback_ticket"). Where("ticket_id = ?", ticket.TicketId). Updates(updatedTicket).Error; err != nil { return fmt.Errorf("failed to update callback ticket: %w", err) } return nil } // FindTimeoutTickets 查询指定状态的超时工单 // @Description 查询指定状态且超过指定时间未处理的工单 // @param status 工单状态 // @param timeoutMicros 超时时间(微秒) // @return []CallbackTicket 超时工单列表 // @return error 错误信息 func (s *CallbackTicketService) FindTimeoutTickets(status string, timeoutMicros int64) ([]CallbackTicket, error) { // 计算超时时间点 nowMicros := time.Now().UnixMicro() timeoutBeforeMicros := nowMicros - timeoutMicros // 查询超时的工单 var tickets []CallbackTicket if err := s.db.Table("mf_callback_ticket"). Where("status = ? AND created_at < ? AND (updated_at IS NULL OR updated_at < ?)", status, timeoutBeforeMicros, timeoutBeforeMicros). Find(&tickets).Error; err != nil { return nil, fmt.Errorf("查询超时工单失败: %w", err) } return tickets, nil } // UpdateTicketToTimeout 将工单状态更新为超时 // @Description 将指定工单更新为超时状态,并添加备注信息 // @param ticket 要更新的工单 // @param originalStatus 原工单状态 // @return error 错误信息 func (s *CallbackTicketService) UpdateTicketToTimeout(ticket *CallbackTicket, originalStatus string, agents []AgentInfo) error { if ticket == nil { return fmt.Errorf("ticket cannot be nil") } now := time.Now().UnixMicro() // 1. 更新原工单为超时 updatedTicket := CallbackTicket{ TicketId: ticket.TicketId, Status: TicketStatusTimeout.Enum(), Comment: fmt.Sprintf("%s - 工单状态为 %s 处理超时,系统自动更新为超时状态", ticket.Comment, originalStatus), UpdatedAt: &now, } if err := s.db.Table("mf_callback_ticket"). Where("ticket_id = ?", ticket.TicketId). Updates(updatedTicket).Error; err != nil { return fmt.Errorf("更新工单 %d 状态失败: %w", ticket.TicketId, err) } // 2. 选择新座席 lastAgent := ticket.AgentName newAgent := s.SelectNextAgent(agents, lastAgent) // 查找新座席邮箱 newAgentEmail := "" for _, agent := range agents { if agent.Name == newAgent { newAgentEmail = agent.Email break } } // 3. 创建新工单 newTicket := CallbackTicket{ CallerNumber: ticket.CallerNumber, CalleeNumber: ticket.CalleeNumber, Status: TicketStatusNew.Enum(), AgentName: newAgent, Comment: fmt.Sprintf("由超时工单 %d 自动重建", ticket.TicketId), MsdData: ticket.MsdData, RmUid: ticket.RmUid, CreatedAt: now, UpdatedAt: nil, } if err := s.db.Table("mf_callback_ticket").Create(&newTicket).Error; err != nil { return fmt.Errorf("创建新工单失败: %w", err) } // 新工单分配后发送邮件通知 if newAgentEmail != "" { subject := "新工单自动重建通知" body := fmt.Sprintf("您被分配了一个自动重建的回拨工单,主叫号码:%s", newTicket.CallerNumber) go email.SendEmail(newAgentEmail, subject, body) } return nil }