feat(conversation): support team-pool dispatch fallback
This commit is contained in:
@@ -83,6 +83,42 @@ func (s *conversationHumanDispatchService) ApplyHumanOnlyCreate(conversationID i
|
||||
return s.dispatchAfterHandoff(conversationID, aiAgent.ID, activeTeamIDs, "仅人工模式新会话", false)
|
||||
}
|
||||
|
||||
func (s *conversationHumanDispatchService) DispatchPendingConversation(conversationID int64, aiAgent models.AIAgent) (*HandoffDecisionResult, error) {
|
||||
conversation := ConversationService.Get(conversationID)
|
||||
if conversation == nil {
|
||||
return nil, errorsx.InvalidParam("会话不存在")
|
||||
}
|
||||
if conversation.Status != enums.IMConversationStatusPending || conversation.CurrentAssigneeID > 0 {
|
||||
return nil, errorsx.InvalidParam("只有待接入未分配会话允许自动分配")
|
||||
}
|
||||
activeTeamIDs := ConversationDispatchService.findActiveScheduleTeamIDs(orderedPositiveIDs(aiAgent.TeamIDs), time.Now())
|
||||
if len(activeTeamIDs) == 0 {
|
||||
return &HandoffDecisionResult{Decision: HandoffDecisionOffHours}, nil
|
||||
}
|
||||
candidates, _, err := ConversationDispatchService.pickDispatchCandidates(activeTeamIDs, time.Now())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(candidates) > 0 {
|
||||
dispatched, err := ConversationDispatchService.tryAssignConversation(conversationID, candidates[0].profile, "自动分配")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dispatched != nil {
|
||||
return &HandoffDecisionResult{
|
||||
Decision: HandoffDecisionAssigned,
|
||||
TeamID: dispatched.CurrentTeamID,
|
||||
AssigneeID: dispatched.CurrentAssigneeID,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
teamID := activeTeamIDs[0]
|
||||
if err := s.moveToTeamPool(conversationID, teamID, "手动触发自动分配"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &HandoffDecisionResult{Decision: HandoffDecisionTeamPool, TeamID: teamID}, nil
|
||||
}
|
||||
|
||||
func (s *conversationHumanDispatchService) dispatchAfterHandoff(conversationID, aiAgentID int64, activeTeamIDs []int64, reason string, publishAssignEvent bool) (*HandoffDecisionResult, error) {
|
||||
if err := s.sendAIText(conversationID, aiAgentID, HandoffWaitingMessage); err != nil {
|
||||
return nil, err
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"time"
|
||||
|
||||
"cs-agent/internal/models"
|
||||
"cs-agent/internal/pkg/dto"
|
||||
"cs-agent/internal/pkg/enums"
|
||||
"cs-agent/internal/pkg/openidentity"
|
||||
"cs-agent/internal/services"
|
||||
@@ -149,6 +150,37 @@ func TestConversationHumanDispatchHumanOnlyCreateAssignsAvailableAgent(t *testin
|
||||
}
|
||||
}
|
||||
|
||||
func TestConversationAutoAssignManualDispatchOffHoursReturnsBusinessMessage(t *testing.T) {
|
||||
db := setupConversationHumanDispatchTestDB(t)
|
||||
aiAgent := createHumanDispatchAIAgent(t, db, enums.IMConversationServiceModeAIFirst, "1")
|
||||
conversation := createHumanDispatchConversation(t, db, aiAgent.ID, enums.IMConversationStatusPending)
|
||||
|
||||
err := services.ConversationService.AutoAssignConversation(conversation.ID, testHumanDispatchOperator())
|
||||
if err == nil {
|
||||
t.Fatalf("expected off-hours manual dispatch to fail")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "当前暂不在人工客服服务时间内") {
|
||||
t.Fatalf("expected off-hours error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConversationAutoAssignManualDispatchFallsBackToTeamPool(t *testing.T) {
|
||||
db := setupConversationHumanDispatchTestDB(t)
|
||||
aiAgent := createHumanDispatchAIAgent(t, db, enums.IMConversationServiceModeAIFirst, "1")
|
||||
createHumanDispatchTeam(t, db, 1, "售后支持组")
|
||||
createHumanDispatchActiveSchedule(t, db, 1)
|
||||
conversation := createHumanDispatchConversation(t, db, aiAgent.ID, enums.IMConversationStatusPending)
|
||||
|
||||
err := services.ConversationService.AutoAssignConversation(conversation.ID, testHumanDispatchOperator())
|
||||
if err != nil {
|
||||
t.Fatalf("AutoAssignConversation() error = %v", err)
|
||||
}
|
||||
current := services.ConversationService.Get(conversation.ID)
|
||||
if current.Status != enums.IMConversationStatusPending || current.CurrentTeamID != 1 || current.CurrentAssigneeID != 0 {
|
||||
t.Fatalf("expected team-pool pending conversation, got %+v", current)
|
||||
}
|
||||
}
|
||||
|
||||
func setupConversationHumanDispatchTestDB(t *testing.T) *gorm.DB {
|
||||
t.Helper()
|
||||
dbName := strings.NewReplacer("/", "_", " ", "_").Replace(t.Name())
|
||||
@@ -265,3 +297,7 @@ func createHumanDispatchConversation(t *testing.T, db *gorm.DB, aiAgentID int64,
|
||||
}
|
||||
return item
|
||||
}
|
||||
|
||||
func testHumanDispatchOperator() *dto.AuthPrincipal {
|
||||
return &dto.AuthPrincipal{UserID: 9, Username: "dispatcher", Nickname: "调度员"}
|
||||
}
|
||||
|
||||
@@ -248,12 +248,16 @@ func (s *conversationService) AutoAssignConversation(conversationID int64, opera
|
||||
return errorsx.InvalidParam("当前会话已分配客服")
|
||||
}
|
||||
|
||||
dispatched, err := ConversationDispatchService.DispatchConversation(conversationID)
|
||||
aiAgent := AIAgentService.Get(conversation.AIAgentID)
|
||||
if aiAgent == nil || aiAgent.Status != enums.StatusOk {
|
||||
return errorsx.InvalidParam("AI Agent 不存在或已停用")
|
||||
}
|
||||
result, err := ConversationHumanDispatchService.DispatchPendingConversation(conversationID, *aiAgent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if dispatched == nil {
|
||||
return errorsx.InvalidParam("当前暂无可自动分配的值班客服")
|
||||
if result == nil || result.Decision == HandoffDecisionOffHours {
|
||||
return errorsx.InvalidParam("当前暂不在人工客服服务时间内")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user