Files
be.ems/features/ue/mf_callback_ticket/service.go
2025-06-20 18:43:08 +08:00

385 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}