feat: callback ticket features

This commit is contained in:
zhangsz
2025-06-11 17:24:41 +08:00
parent 647367394d
commit 39e91bbbe0
8 changed files with 890 additions and 1 deletions

View File

@@ -0,0 +1,353 @@
package mf_callback_ticket
import (
"fmt"
"time"
"be.ems/lib/dborm"
"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 []struct {
Domain string `json:"domain"`
Index int `json:"index"`
Name string `json:"name"`
Online bool `json:"online"`
Password string `json:"password"`
}, 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) error {
if ticket == nil {
return fmt.Errorf("ticket cannot be nil")
}
now := time.Now().UnixMicro()
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)
}
return nil
}