feat: harden support, localize knowledge and add operator availability [skip ci]

Source-only publication of reviewed commit 64e96a7f423673dc29ad56485be9fc79bce8af1c.

Passed in isolation: focused frontend/backend tests, race checks, TypeScript typecheck, lint, Go compilation, Flowgram build, and deterministic SDK build/tests.

Not accepted as a production release: Next.js export/native assembly remains blocked by environment memory limits; browser interaction and MySQL staging acceptance remain unverified. No production server, database/schema, runtime configuration, deployment, release tag, or workflow execution was performed.

Expected source tree: 0a4a7528e9.
This commit is contained in:
2026-10-16 15:21:42 +00:00
parent 299ea50fb5
commit 2f7a491666
176 changed files with 11869 additions and 2716 deletions

View File

@@ -1,3 +1,4 @@
// Modified in AgentDeskNext: authenticated support availability and calendars.
package services
import (
@@ -8,6 +9,7 @@ import (
"agent-desk/internal/pkg/errorsx"
"agent-desk/internal/pkg/utils"
"agent-desk/internal/repositories"
"strconv"
"strings"
"time"
@@ -79,11 +81,18 @@ func (s *agentProfileService) GetUserIDsByTeamID(teamID int64) []int64 {
// GetDispatchAgents 获取可用于分配会话的客服
func (s *agentProfileService) GetDispatchAgents(teamIds []int64) []models.AgentProfile {
return AgentProfileService.Find(sqls.NewCnd().
In("team_id", teamIds).
Eq("status", enums.StatusOk).
Eq("auto_assign_enabled", true).
Eq("service_status", enums.ServiceStatusIdle))
profiles, err := repositories.SupportAvailabilityRepository.Profiles(sqls.DB(), teamIds)
if err != nil {
return nil
}
result := make([]models.AgentProfile, 0, len(profiles))
for _, profile := range profiles {
eligibility, err := SupportAvailabilityService.Evaluate(sqls.DB(), profile, true, time.Now(), 0)
if err == nil && eligibility.Eligible {
result = append(result, profile)
}
}
return result
}
func (s *agentProfileService) CreateAgentProfile(req request.CreateAgentProfileRequest, operator *dto.AuthPrincipal) (*models.AgentProfile, error) {
@@ -115,12 +124,12 @@ func (s *agentProfileService) UpdateAgentProfile(req request.UpdateAgentProfileR
return err
}
if err := repositories.AgentProfileRepository.Updates(sqls.DB(), req.ID, map[string]any{
"user_id": item.UserID,
"team_id": item.TeamID,
"agent_code": item.AgentCode,
"display_name": item.DisplayName,
"avatar": item.Avatar,
"service_status": item.ServiceStatus,
"user_id": item.UserID,
"team_id": item.TeamID,
"agent_code": item.AgentCode,
"display_name": item.DisplayName,
"avatar": item.Avatar,
// Availability is managed only through the dedicated guarded endpoint.
"max_concurrent_count": item.MaxConcurrentCount,
"priority_level": item.PriorityLevel,
"auto_assign_enabled": item.AutoAssignEnabled,
@@ -169,7 +178,7 @@ func (s *agentProfileService) buildProfileModel(id int64, req request.CreateAgen
if exists := s.Take("agent_code = ? AND id <> ?", req.AgentCode, id); exists != nil {
return nil, errorsx.InvalidParamI18n("error.e0163")
}
if !enums.IsValidServiceStatus(req.ServiceStatus) {
if !enums.IsValidServiceStatus(req.ServiceStatus) || (id == 0 && req.ServiceStatus == enums.ServiceStatusVacation) {
return nil, errorsx.InvalidParamI18n("error.e0165")
}
if req.MaxConcurrentCount < 0 {
@@ -197,11 +206,23 @@ func (s *agentProfileService) dispatchPendingConversationsIfEligible(item *model
if item.Status != enums.StatusOk {
return
}
if !item.AutoAssignEnabled || item.MaxConcurrentCount <= 0 {
if !item.AutoAssignEnabled {
return
}
if item.ServiceStatus != enums.ServiceStatusIdle {
if effectiveSupportStatus(*item, time.Now()) != enums.ServiceStatusIdle {
return
}
_, _ = ConversationDispatchService.DispatchPendingConversations(0)
}
func (s *agentProfileService) FilterEffectiveServiceStatus(cnd *sqls.Cnd, value string) error {
if strings.TrimSpace(value) == "" {
return nil
}
number, err := strconv.Atoi(value)
if err != nil || !enums.IsValidServiceStatus(enums.ServiceStatus(number)) {
return errorsx.InvalidParamI18n("error.supportAvailability.invalidStatus")
}
repositories.SupportAvailabilityRepository.EffectiveStatusCondition(cnd, enums.ServiceStatus(number), time.Now())
return nil
}