feat: ticket enhancemnet

This commit is contained in:
zhangsz
2025-07-02 17:22:35 +08:00
parent 24ed4e874a
commit 758276ecfc
11 changed files with 577 additions and 108 deletions

View File

@@ -4,8 +4,10 @@ import (
"fmt"
"time"
"be.ems/lib/config"
"be.ems/lib/dborm"
"be.ems/lib/email"
"be.ems/lib/log"
"gorm.io/gorm"
)
@@ -97,7 +99,7 @@ func (s *CallbackTicketService) SelectCallbackTicketByPage(pageNum int, pageSize
return tickets, int(total), nil
}
func (s *CallbackTicketService) InsertCallbackTicket(ticket CallbackTicket) error {
func (s *CallbackTicketService) InsertCallbackTicket(ticket *CallbackTicket) error {
// 判断主叫号码是否已存在未处理完的工单
var existingCount int64
if err := s.getDB().Table("mf_callback_ticket").
@@ -110,7 +112,7 @@ func (s *CallbackTicketService) InsertCallbackTicket(ticket CallbackTicket) erro
return fmt.Errorf("caller %s already has a pending ticket", ticket.CallerNumber)
}
// 这里可以使用ORM或其他方式将ticket插入到数据库中
if err := s.getDB().Table("mf_callback_ticket").Create(&ticket).Error; err != nil {
if err := s.getDB().Table("mf_callback_ticket").Create(ticket).Error; err != nil {
return fmt.Errorf("failed to insert callback ticket: %w", err)
}
return nil
@@ -174,7 +176,7 @@ func (s *CallbackTicketService) GetLastAssignedAgent() (string, error) {
var lastTicket CallbackTicket
if err := s.getDB().Table("mf_callback_ticket").
Where("agent_name <> ''").
Order("created_at DESC").
Order("created_at DESC, ticket_id DESC").
First(&lastTicket).Error; err != nil {
if err == gorm.ErrRecordNotFound {
// 没有记录属于正常情况,返回空字符串
@@ -377,14 +379,137 @@ func (s *CallbackTicketService) UpdateTicketToTimeout(ticket *CallbackTicket, or
// 新工单分配后发送邮件通知
if newAgent.Email != "" {
subject := "新工单自动重建通知"
body := fmt.Sprintf("您被分配了一个自动重建的回拨工单,主叫号码:%s", newTicket.CallerNumber)
go email.SendEmail(newAgent.Email, subject, body)
// 发送邮件通知
// 获取SMTP配置
emailConfig := config.GetSMTPConfig()
if emailConfig != nil && emailConfig.Enabled {
// 创建配置副本,避免修改全局配置
emailCopy := *emailConfig // 浅拷贝结构体
// 合并配置中的To地址和当前工单的座席邮箱
var recipients []string
// 添加配置中的原始收件人(如管理员、监控人员等)
if len(emailConfig.To) > 0 {
recipients = append(recipients, emailConfig.To...)
}
// 添加当前工单的座席邮箱
recipients = append(recipients, ticket.AgentEmail)
// 去重处理(避免重复邮箱)
emailCopy.To = email.RemoveDuplicateEmails(recipients)
// 设置邮件主题和内容
emailCopy.Subject = "新工单自动重建通知"
emailCopy.Body = fmt.Sprintf("您被分配了一个自动重建的回拨工单ID: %d, 主叫号码:%s, 请及时处理。",
newTicket.TicketId, newTicket.CallerNumber)
go email.SendEmailWithGomail(emailCopy)
}
}
}
return nil
}
// BatchUpdateTimeoutTickets 批量处理超时工单
func (s *CallbackTicketService) BatchUpdateTimeoutTickets(tickets []CallbackTicket, originalStatus string, agents []AgentInfo) error {
if len(tickets) == 0 {
return nil
}
// 获取当前最后分配的座席
lastAssignedAgent, err := s.GetLastAssignedAgent()
if err != nil {
log.Errorf("获取最后分配座席失败: %v", err)
lastAssignedAgent = ""
}
now := time.Now().UnixMicro()
var successCount int
for i, ticket := range tickets {
// 1. 更新原工单为超时
updatedTicket := CallbackTicket{
TicketId: ticket.TicketId,
Status: TicketStatusTimeout.Enum(),
Comment: fmt.Sprintf("%s - 工单状态为 %s 处理超时,系统自动更新为超时状态", ticket.Comment, originalStatus),
UpdatedAt: &now,
}
if err := s.getDB().Table("mf_callback_ticket").
Where("ticket_id = ?", ticket.TicketId).
Updates(updatedTicket).Error; err != nil {
log.Errorf("更新工单 %d 状态失败: %v", ticket.TicketId, err)
continue
}
// 2. 选择下一个座席使用批处理中维护的lastAssignedAgent
newAgent := s.SelectNextAgent(agents, lastAssignedAgent)
if newAgent == nil {
log.Errorf("没有可用的座席分配给工单 %d", ticket.TicketId)
continue
}
// 3. 创建新工单
newTicket := CallbackTicket{
CallerNumber: ticket.CallerNumber,
CalleeNumber: ticket.CalleeNumber,
Status: TicketStatusNew.Enum(),
AgentName: newAgent.Name,
AgentEmail: newAgent.Email,
AgentMobile: newAgent.Mobile,
Comment: fmt.Sprintf("由超时工单 %d 自动重建", ticket.TicketId),
MsdData: ticket.MsdData,
RmUid: ticket.RmUid,
CreatedAt: now + int64(i), // 确保每个工单的创建时间不同,
UpdatedAt: nil,
}
if err := s.getDB().Table("mf_callback_ticket").Create(&newTicket).Error; err != nil {
log.Errorf("创建新工单失败: %v", err)
continue
}
// 4. 更新最后分配的座席
lastAssignedAgent = newAgent.Name
// 5. 发送邮件通知
if newAgent.Email != "" {
// 发送邮件通知
// 获取SMTP配置
emailConfig := config.GetSMTPConfig()
if emailConfig != nil && emailConfig.Enabled {
// 创建配置副本,避免修改全局配置
emailCopy := *emailConfig // 浅拷贝结构体
// 合并配置中的To地址和当前工单的座席邮箱
var recipients []string
// 添加配置中的原始收件人(如管理员、监控人员等)
if len(emailConfig.To) > 0 {
recipients = append(recipients, emailConfig.To...)
}
// 添加当前工单的座席邮箱
recipients = append(recipients, ticket.AgentEmail)
// 去重处理(避免重复邮箱)
emailCopy.To = email.RemoveDuplicateEmails(recipients)
// 设置邮件主题和内容
emailCopy.Subject = "新工单自动重建通知"
emailCopy.Body = fmt.Sprintf("您被分配了一个自动重建的回拨工单ID: %d, 主叫号码:%s, 请及时处理。",
newTicket.TicketId, newTicket.CallerNumber)
go email.SendEmailWithGomail(emailCopy)
}
}
successCount++
log.Infof("工单 %d 已重建为新工单 %d分配给座席 %s (第%d个处理)",
ticket.TicketId, newTicket.TicketId, newAgent.Name, i+1)
}
log.Infof("批量处理完成,成功处理 %d/%d 个超时工单", successCount, len(tickets))
return nil
}
// FindNearlyTimeoutTickets 查询即将超时的工单
func (s *CallbackTicketService) FindNearlyTimeoutTickets(status string, timeoutMicros int64, aheadMicros int64) ([]CallbackTicket, error) {
nowMicros := time.Now().UnixMicro()