385 lines
13 KiB
Go
385 lines
13 KiB
Go
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
|
||
}
|