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 "" }