Files
ai-agent/internal/builders/ticket_builder.go
T
mlogclub efe801b8bf Init
2026-04-09 10:01:23 +08:00

568 lines
18 KiB
Go

package builders
import (
"cs-agent/internal/models"
"cs-agent/internal/pkg/dto/response"
"cs-agent/internal/pkg/enums"
"cs-agent/internal/pkg/utils"
"cs-agent/internal/services"
"encoding/json"
"strings"
"github.com/mlogclub/simple/sqls"
)
type TicketBuildContext struct {
TagsByTicketID map[int64][]models.Tag
Priorities map[int64]*models.TicketPriorityConfig
ResolutionCodes map[string]*models.TicketResolutionCode
Users map[int64]*models.User
Teams map[int64]*models.AgentTeam
Customers map[int64]*models.Customer
SLAByTicketID map[int64][]models.TicketSLARecord
WatchedTicketIDs map[int64]struct{}
}
type TicketDetailBuildContext struct {
Users map[int64]*models.User
Teams map[int64]*models.AgentTeam
AgentProfiles map[int64]*models.AgentProfile
RelatedTickets map[int64]*models.Ticket
Customers map[int64]*models.Customer
AIAgents map[int64]*models.AIAgent
}
func BuildTicket(item *models.Ticket) *response.TicketResponse {
return BuildTicketWithContext(item, nil)
}
func BuildTicketWithContext(item *models.Ticket, ctx *TicketBuildContext) *response.TicketResponse {
if item == nil {
return nil
}
ret := &response.TicketResponse{
ID: item.ID,
TicketNo: item.TicketNo,
Title: item.Title,
Description: item.Description,
Source: item.Source,
Channel: item.Channel,
CustomerID: item.CustomerID,
ConversationID: item.ConversationID,
Type: item.Type,
Priority: item.Priority,
Severity: item.Severity,
Status: item.Status,
CurrentTeamID: item.CurrentTeamID,
CurrentAssigneeID: item.CurrentAssigneeID,
PendingReason: item.PendingReason,
CloseReason: item.CloseReason,
ResolutionCode: item.ResolutionCode,
ResolutionSummary: item.ResolutionSummary,
FirstResponseAt: utils.FormatTimePtr(item.FirstResponseAt),
ResolvedAt: utils.FormatTimePtr(item.ResolvedAt),
ClosedAt: utils.FormatTimePtr(item.ClosedAt),
DueAt: utils.FormatTimePtr(item.DueAt),
NextReplyDeadlineAt: utils.FormatTimePtr(item.NextReplyDeadlineAt),
ResolveDeadlineAt: utils.FormatTimePtr(item.ResolveDeadlineAt),
ReopenedCount: item.ReopenedCount,
CreatedAt: utils.FormatTime(item.CreatedAt),
UpdatedAt: utils.FormatTime(item.UpdatedAt),
}
if ctx != nil {
if _, ok := ctx.WatchedTicketIDs[item.ID]; ok {
ret.WatchedByMe = true
}
}
if ctx != nil && ctx.TagsByTicketID != nil {
ret.Tags = BuildTagResponses(ctx.TagsByTicketID[item.ID])
} else {
ret.Tags = BuildTagResponses(services.TicketService.GetTags(item.ID))
}
if item.Priority > 0 && ctx != nil && ctx.Priorities != nil {
if priority := ctx.Priorities[item.Priority]; priority != nil {
ret.PriorityName = priority.Name
}
} else if item.Priority > 0 {
if priority := services.TicketPriorityConfigService.Get(item.Priority); priority != nil {
ret.PriorityName = priority.Name
}
}
if item.ResolutionCode != "" && ctx != nil && ctx.ResolutionCodes != nil {
if code := ctx.ResolutionCodes[item.ResolutionCode]; code != nil {
ret.ResolutionCodeName = code.Name
}
} else if item.ResolutionCode != "" {
if code := services.TicketResolutionCodeService.Take("code = ? AND status <> ?", item.ResolutionCode, enums.StatusDeleted); code != nil {
ret.ResolutionCodeName = code.Name
}
}
if item.CurrentAssigneeID > 0 && ctx != nil && ctx.Users != nil {
if user := ctx.Users[item.CurrentAssigneeID]; user != nil {
ret.CurrentAssigneeName = user.Nickname
if ret.CurrentAssigneeName == "" {
ret.CurrentAssigneeName = user.Username
}
}
} else if item.CurrentAssigneeID > 0 {
if user := services.UserService.Get(item.CurrentAssigneeID); user != nil {
ret.CurrentAssigneeName = user.Nickname
if ret.CurrentAssigneeName == "" {
ret.CurrentAssigneeName = user.Username
}
}
}
if item.CurrentTeamID > 0 && ctx != nil && ctx.Teams != nil {
if team := ctx.Teams[item.CurrentTeamID]; team != nil {
ret.CurrentTeamName = team.Name
}
} else if item.CurrentTeamID > 0 {
if team := services.AgentTeamService.Get(item.CurrentTeamID); team != nil {
ret.CurrentTeamName = team.Name
}
}
if item.CustomerID > 0 && ctx != nil && ctx.Customers != nil {
ret.Customer = BuildCustomer(ctx.Customers[item.CustomerID])
} else if item.CustomerID > 0 {
ret.Customer = BuildCustomer(services.CustomerService.Get(item.CustomerID))
}
if ctx != nil && ctx.SLAByTicketID != nil {
ret.SLA = BuildTicketSLAList(ctx.SLAByTicketID[item.ID])
} else {
ret.SLA = BuildTicketSLAList(
services.TicketSLARecordService.Find(
sqls.NewCnd().Eq("ticket_id", item.ID).Asc("id"),
),
)
}
return ret
}
func BuildTicketList(list []models.Ticket) []response.TicketResponse {
return BuildTicketListWithContext(list, nil)
}
func BuildTicketListWithContext(list []models.Ticket, ctx *TicketBuildContext) []response.TicketResponse {
if len(list) == 0 {
return nil
}
results := make([]response.TicketResponse, 0, len(list))
for i := range list {
if item := BuildTicketWithContext(&list[i], ctx); item != nil {
results = append(results, *item)
}
}
return results
}
func BuildTicketComment(item *models.TicketComment) *response.TicketCommentResponse {
return BuildTicketCommentWithContext(item, nil)
}
func BuildTicketCommentWithContext(item *models.TicketComment, ctx *TicketDetailBuildContext) *response.TicketCommentResponse {
if item == nil {
return nil
}
ret := &response.TicketCommentResponse{
ID: item.ID,
TicketID: item.TicketID,
CommentType: item.CommentType,
AuthorType: item.AuthorType,
AuthorID: item.AuthorID,
ContentType: item.ContentType,
Content: item.Content,
Payload: item.Payload,
CreatedAt: utils.FormatTime(item.CreatedAt),
}
ret.AuthorName = buildTicketOperatorName(item.AuthorType, item.AuthorID, ctx)
return ret
}
func BuildTicketCommentList(list []models.TicketComment) []response.TicketCommentResponse {
return BuildTicketCommentListWithContext(list, nil)
}
func BuildTicketCommentListWithContext(list []models.TicketComment, ctx *TicketDetailBuildContext) []response.TicketCommentResponse {
if len(list) == 0 {
return nil
}
results := make([]response.TicketCommentResponse, 0, len(list))
for i := range list {
if item := BuildTicketCommentWithContext(&list[i], ctx); item != nil {
results = append(results, *item)
}
}
return results
}
func BuildTicketEventLog(item *models.TicketEventLog) *response.TicketEventLogResponse {
return BuildTicketEventLogWithContext(item, nil)
}
func BuildTicketEventLogWithContext(item *models.TicketEventLog, ctx *TicketDetailBuildContext) *response.TicketEventLogResponse {
if item == nil {
return nil
}
return &response.TicketEventLogResponse{
ID: item.ID,
TicketID: item.TicketID,
EventType: item.EventType,
OperatorType: item.OperatorType,
OperatorID: item.OperatorID,
OperatorName: buildTicketOperatorName(item.OperatorType, item.OperatorID, ctx),
OldValue: item.OldValue,
NewValue: item.NewValue,
Content: item.Content,
Payload: item.Payload,
CreatedAt: utils.FormatTime(item.CreatedAt),
}
}
func BuildTicketEventLogList(list []models.TicketEventLog) []response.TicketEventLogResponse {
return BuildTicketEventLogListWithContext(list, nil)
}
func BuildTicketEventLogListWithContext(list []models.TicketEventLog, ctx *TicketDetailBuildContext) []response.TicketEventLogResponse {
if len(list) == 0 {
return nil
}
results := make([]response.TicketEventLogResponse, 0, len(list))
for i := range list {
if item := BuildTicketEventLogWithContext(&list[i], ctx); item != nil {
results = append(results, *item)
}
}
return results
}
func BuildTicketSLAList(list []models.TicketSLARecord) []response.TicketSLAResponse {
if len(list) == 0 {
return nil
}
results := make([]response.TicketSLAResponse, 0, len(list))
for i := range list {
item := &list[i]
results = append(results, response.TicketSLAResponse{
SLAType: item.SLAType,
TargetMinutes: item.TargetMinutes,
Status: item.Status,
StartedAt: utils.FormatTimePtr(item.StartedAt),
PausedAt: utils.FormatTimePtr(item.PausedAt),
StoppedAt: utils.FormatTimePtr(item.StoppedAt),
BreachedAt: utils.FormatTimePtr(item.BreachedAt),
ElapsedMin: item.ElapsedMin,
})
}
return results
}
func BuildTicketDetail(aggregate *services.TicketDetailAggregate) *response.TicketDetailResponse {
return BuildTicketDetailWithContext(aggregate, nil)
}
func BuildTicketDetailWithContext(aggregate *services.TicketDetailAggregate, ctx *TicketDetailBuildContext) *response.TicketDetailResponse {
if aggregate == nil || aggregate.Ticket == nil {
return nil
}
if ctx == nil {
users := make(map[int64]*models.User, len(aggregate.Users)+len(aggregate.OperatorUsers))
for id, item := range aggregate.Users {
users[id] = item
}
for id, item := range aggregate.OperatorUsers {
users[id] = item
}
ctx = &TicketDetailBuildContext{
Users: users,
Teams: aggregate.Teams,
AgentProfiles: aggregate.AgentProfiles,
RelatedTickets: aggregate.RelatedMap,
Customers: aggregate.OperatorCustomers,
AIAgents: aggregate.OperatorAIAgents,
}
}
ret := &response.TicketDetailResponse{
Ticket: *BuildTicket(aggregate.Ticket),
Watchers: BuildTicketWatcherListWithContext(aggregate.Watchers, ctx),
Collaborators: BuildTicketCollaboratorListWithContext(aggregate.Collaborators, ctx),
RelatedTickets: BuildTicketRelationListWithContext(aggregate.RelatedTickets, ctx),
}
if len(aggregate.Comments) > 0 {
ret.Comments = make([]response.TicketCommentResponse, 0, len(aggregate.Comments))
for i := range aggregate.Comments {
if item := BuildTicketCommentWithContext(&aggregate.Comments[i], ctx); item != nil {
ret.Comments = append(ret.Comments, *item)
}
}
}
if len(aggregate.Events) > 0 {
ret.Events = make([]response.TicketEventLogResponse, 0, len(aggregate.Events))
for i := range aggregate.Events {
if item := BuildTicketEventLogWithContext(&aggregate.Events[i], ctx); item != nil {
ret.Events = append(ret.Events, *item)
}
}
}
if aggregate.Customer != nil {
ret.Ticket.Customer = BuildCustomer(aggregate.Customer)
}
ret.Ticket.Tags = BuildTagResponses(aggregate.Tags)
ret.Ticket.SLA = BuildTicketSLAList(aggregate.SLAs)
for i := range ret.Comments {
if ret.Comments[i].AuthorName == "" {
ret.Comments[i].AuthorName = buildTicketOperatorName(ret.Comments[i].AuthorType, ret.Comments[i].AuthorID, ctx)
}
}
for i := range ret.Events {
if ret.Events[i].OperatorName == "" {
ret.Events[i].OperatorName = buildTicketOperatorName(ret.Events[i].OperatorType, ret.Events[i].OperatorID, ctx)
}
}
return ret
}
func BuildTicketRelation(item *models.TicketRelation) *response.TicketRelationResponse {
return BuildTicketRelationWithContext(item, nil)
}
func BuildTicketRelationWithContext(item *models.TicketRelation, ctx *TicketDetailBuildContext) *response.TicketRelationResponse {
if item == nil {
return nil
}
ret := &response.TicketRelationResponse{
ID: item.ID,
TicketID: item.TicketID,
RelatedTicketID: item.RelatedTicketID,
RelationType: item.RelationType,
}
related := (*models.Ticket)(nil)
if ctx != nil && ctx.RelatedTickets != nil {
related = ctx.RelatedTickets[item.RelatedTicketID]
}
if related == nil {
related = services.TicketService.Get(item.RelatedTicketID)
}
if related != nil {
ret.RelatedTicketNo = related.TicketNo
ret.RelatedTicketTitle = related.Title
ret.RelatedTicketStatus = related.Status
ret.UpdatedAt = utils.FormatTime(related.UpdatedAt)
if related.CurrentTeamID > 0 && ctx != nil && ctx.Teams != nil {
if team := ctx.Teams[related.CurrentTeamID]; team != nil {
ret.CurrentTeamName = team.Name
}
} else if related.CurrentTeamID > 0 {
if team := services.AgentTeamService.Get(related.CurrentTeamID); team != nil {
ret.CurrentTeamName = team.Name
}
}
if related.CurrentAssigneeID > 0 && ctx != nil && ctx.Users != nil {
ret.CurrentAssigneeName = buildTicketUserDisplayName(ctx.Users[related.CurrentAssigneeID])
} else if related.CurrentAssigneeID > 0 {
if user := services.UserService.Get(related.CurrentAssigneeID); user != nil {
ret.CurrentAssigneeName = user.Nickname
if ret.CurrentAssigneeName == "" {
ret.CurrentAssigneeName = user.Username
}
}
}
}
return ret
}
func BuildTicketRelationList(list []models.TicketRelation) []response.TicketRelationResponse {
return BuildTicketRelationListWithContext(list, nil)
}
func BuildTicketRelationListWithContext(list []models.TicketRelation, ctx *TicketDetailBuildContext) []response.TicketRelationResponse {
if len(list) == 0 {
return nil
}
results := make([]response.TicketRelationResponse, 0, len(list))
for i := range list {
if item := BuildTicketRelationWithContext(&list[i], ctx); item != nil {
results = append(results, *item)
}
}
return results
}
func BuildTicketSummary(summary *services.TicketSummaryAggregate) *response.TicketSummaryResponse {
if summary == nil {
return nil
}
return &response.TicketSummaryResponse{
All: summary.All,
Mine: summary.Mine,
Watching: summary.Watching,
Collaboration: summary.Collaboration,
Participating: summary.Participating,
Mentioned: summary.Mentioned,
Unassigned: summary.Unassigned,
PendingCustomer: summary.PendingCustomer,
PendingInternal: summary.PendingInternal,
Overdue: summary.Overdue,
}
}
func BuildTicketRiskOverview(overview *services.TicketRiskOverviewAggregate) *response.TicketRiskOverviewResponse {
if overview == nil {
return nil
}
ret := &response.TicketRiskOverviewResponse{
Overdue: overview.Overdue,
HighRisk: overview.HighRisk,
Unassigned: overview.Unassigned,
PendingInternal: overview.PendingInternal,
PendingCustomer: overview.PendingCustomer,
RiskWindowMins: overview.RiskWindowMins,
}
if len(overview.Reasons) > 0 {
ret.Reasons = make([]response.TicketRiskReasonResponse, 0, len(overview.Reasons))
for _, item := range overview.Reasons {
ret.Reasons = append(ret.Reasons, response.TicketRiskReasonResponse{
Code: item.Code,
Title: item.Title,
Description: item.Description,
Count: item.Count,
})
}
}
return ret
}
func BuildTicketWatcherList(list []models.TicketWatcher) []response.TicketWatcherResponse {
return BuildTicketWatcherListWithContext(list, nil)
}
func BuildTicketWatcherListWithContext(list []models.TicketWatcher, ctx *TicketDetailBuildContext) []response.TicketWatcherResponse {
if len(list) == 0 {
return nil
}
results := make([]response.TicketWatcherResponse, 0, len(list))
for i := range list {
item := &list[i]
out := response.TicketWatcherResponse{
ID: item.ID,
UserID: item.UserID,
}
if ctx != nil && ctx.Users != nil {
out.UserName = buildTicketUserDisplayName(ctx.Users[item.UserID])
} else if user := services.UserService.Get(item.UserID); user != nil {
out.UserName = buildTicketUserDisplayName(user)
}
results = append(results, out)
}
return results
}
func BuildTicketCollaboratorList(list []models.TicketCollaborator) []response.TicketCollaboratorResponse {
return BuildTicketCollaboratorListWithContext(list, nil)
}
func BuildTicketCollaboratorListWithContext(list []models.TicketCollaborator, ctx *TicketDetailBuildContext) []response.TicketCollaboratorResponse {
if len(list) == 0 {
return nil
}
results := make([]response.TicketCollaboratorResponse, 0, len(list))
for i := range list {
item := &list[i]
out := response.TicketCollaboratorResponse{
ID: item.ID,
UserID: item.UserID,
}
if ctx != nil && ctx.Users != nil {
out.UserName = buildTicketUserDisplayName(ctx.Users[item.UserID])
} else if user := services.UserService.Get(item.UserID); user != nil {
out.UserName = buildTicketUserDisplayName(user)
}
if ctx != nil && ctx.AgentProfiles != nil {
if profile := ctx.AgentProfiles[item.UserID]; profile != nil && profile.TeamID > 0 {
if team := ctx.Teams[profile.TeamID]; team != nil {
out.TeamName = team.Name
}
}
} else if profile := services.AgentProfileService.GetByUserID(item.UserID); profile != nil && profile.TeamID > 0 {
if team := services.AgentTeamService.Get(profile.TeamID); team != nil {
out.TeamName = team.Name
}
}
results = append(results, out)
}
return results
}
func BuildTicketView(item *models.TicketView) *response.TicketViewResponse {
if item == nil {
return nil
}
ret := &response.TicketViewResponse{
ID: item.ID,
Name: item.Name,
SortNo: item.SortNo,
}
if strings.TrimSpace(item.FiltersJSON) != "" {
_ = json.Unmarshal([]byte(item.FiltersJSON), &ret.Filters)
}
return ret
}
func BuildTicketViewList(list []models.TicketView) []response.TicketViewResponse {
if len(list) == 0 {
return nil
}
results := make([]response.TicketViewResponse, 0, len(list))
for i := range list {
if item := BuildTicketView(&list[i]); item != nil {
results = append(results, *item)
}
}
return results
}
func buildTicketUserDisplayName(user *models.User) string {
if user == nil {
return ""
}
if user.Nickname != "" {
return user.Nickname
}
return user.Username
}
func buildTicketOperatorName(senderType enums.IMSenderType, senderID int64, ctx *TicketDetailBuildContext) string {
if senderID <= 0 {
return ""
}
switch senderType {
case enums.IMSenderTypeAgent:
if ctx != nil && ctx.Users != nil {
if user := ctx.Users[senderID]; user != nil {
return buildTicketUserDisplayName(user)
}
}
if user := services.UserService.Get(senderID); user != nil {
return buildTicketUserDisplayName(user)
}
case enums.IMSenderTypeCustomer:
if ctx != nil && ctx.Customers != nil {
if customer := ctx.Customers[senderID]; customer != nil {
return customer.Name
}
}
if customer := services.CustomerService.Get(senderID); customer != nil {
return customer.Name
}
case enums.IMSenderTypeAI:
if ctx != nil && ctx.AIAgents != nil {
if ai := ctx.AIAgents[senderID]; ai != nil {
return ai.Name
}
}
if ai := services.AIAgentService.Get(senderID); ai != nil {
return ai.Name
}
}
return ""
}