refactor(ticket): contract backend ticket core
This commit is contained in:
@@ -49,13 +49,9 @@ func main() {
|
|||||||
codegen.GetGenerateStruct(&models.ConversationEventLog{}),
|
codegen.GetGenerateStruct(&models.ConversationEventLog{}),
|
||||||
codegen.GetGenerateStruct(&models.Ticket{}),
|
codegen.GetGenerateStruct(&models.Ticket{}),
|
||||||
codegen.GetGenerateStruct(&models.TicketTag{}),
|
codegen.GetGenerateStruct(&models.TicketTag{}),
|
||||||
codegen.GetGenerateStruct(&models.TicketResolutionCode{}),
|
codegen.GetGenerateStruct(&models.TicketProgress{}),
|
||||||
codegen.GetGenerateStruct(&models.TicketPriorityConfig{}),
|
codegen.GetGenerateStruct(&models.TicketView{}),
|
||||||
codegen.GetGenerateStruct(&models.TicketComment{}),
|
codegen.GetGenerateStruct(&models.TicketNoSequence{}),
|
||||||
codegen.GetGenerateStruct(&models.TicketWatcher{}),
|
|
||||||
codegen.GetGenerateStruct(&models.TicketEventLog{}),
|
|
||||||
codegen.GetGenerateStruct(&models.TicketSLARecord{}),
|
|
||||||
codegen.GetGenerateStruct(&models.TicketRelation{}),
|
|
||||||
codegen.GetGenerateStruct(&models.AgentProfile{}),
|
codegen.GetGenerateStruct(&models.AgentProfile{}),
|
||||||
codegen.GetGenerateStruct(&models.AgentTeam{}),
|
codegen.GetGenerateStruct(&models.AgentTeam{}),
|
||||||
codegen.GetGenerateStruct(&models.AgentTeamSchedule{}),
|
codegen.GetGenerateStruct(&models.AgentTeamSchedule{}),
|
||||||
|
|||||||
@@ -27,8 +27,6 @@ type CreateTicketGraphInterruptInfo struct {
|
|||||||
type createTicketGraphArgs struct {
|
type createTicketGraphArgs struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Priority int64 `json:"priority"`
|
|
||||||
Severity int `json:"severity"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -100,8 +98,7 @@ func (g *CreateTicketGraph) Run(ctx context.Context, argumentsInJSON string) (st
|
|||||||
|
|
||||||
func (g *CreateTicketGraph) buildCreateRequest(argumentsInJSON string) (request.CreateTicketFromConversationRequest, error) {
|
func (g *CreateTicketGraph) buildCreateRequest(argumentsInJSON string) (request.CreateTicketFromConversationRequest, error) {
|
||||||
req := request.CreateTicketFromConversationRequest{
|
req := request.CreateTicketFromConversationRequest{
|
||||||
ConversationID: g.conversation.ID,
|
ConversationID: g.conversation.ID,
|
||||||
SyncToConversation: true,
|
|
||||||
}
|
}
|
||||||
var args createTicketGraphArgs
|
var args createTicketGraphArgs
|
||||||
if strings.TrimSpace(argumentsInJSON) != "" {
|
if strings.TrimSpace(argumentsInJSON) != "" {
|
||||||
@@ -111,8 +108,6 @@ func (g *CreateTicketGraph) buildCreateRequest(argumentsInJSON string) (request.
|
|||||||
}
|
}
|
||||||
req.Title = strings.TrimSpace(args.Title)
|
req.Title = strings.TrimSpace(args.Title)
|
||||||
req.Description = strings.TrimSpace(args.Description)
|
req.Description = strings.TrimSpace(args.Description)
|
||||||
req.Priority = args.Priority
|
|
||||||
req.Severity = args.Severity
|
|
||||||
if req.Title == "" {
|
if req.Title == "" {
|
||||||
req.Title = strings.TrimSpace(g.conversation.LastMessageSummary)
|
req.Title = strings.TrimSpace(g.conversation.LastMessageSummary)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,16 +18,12 @@ type PrepareTicketDraftInput struct {
|
|||||||
Impact string `json:"impact"`
|
Impact string `json:"impact"`
|
||||||
ExpectedOutcome string `json:"expectedOutcome"`
|
ExpectedOutcome string `json:"expectedOutcome"`
|
||||||
CurrentAttempt string `json:"currentAttempt"`
|
CurrentAttempt string `json:"currentAttempt"`
|
||||||
Priority int64 `json:"priority"`
|
|
||||||
Severity int `json:"severity"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type PrepareTicketDraftResult struct {
|
type PrepareTicketDraftResult struct {
|
||||||
Ready bool `json:"ready"`
|
Ready bool `json:"ready"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Priority int64 `json:"priority,omitempty"`
|
|
||||||
Severity int `json:"severity,omitempty"`
|
|
||||||
MissingFields []string `json:"missingFields,omitempty"`
|
MissingFields []string `json:"missingFields,omitempty"`
|
||||||
FollowUpQuestions []string `json:"followUpQuestions,omitempty"`
|
FollowUpQuestions []string `json:"followUpQuestions,omitempty"`
|
||||||
ConversationFacts []string `json:"conversationFacts,omitempty"`
|
ConversationFacts []string `json:"conversationFacts,omitempty"`
|
||||||
@@ -74,8 +70,6 @@ func (g *PrepareTicketDraftGraph) parseInput(argumentsInJSON string) (PrepareTic
|
|||||||
|
|
||||||
func buildPrepareTicketDraftResult(conversation models.Conversation, messages []models.Message, input PrepareTicketDraftInput) PrepareTicketDraftResult {
|
func buildPrepareTicketDraftResult(conversation models.Conversation, messages []models.Message, input PrepareTicketDraftInput) PrepareTicketDraftResult {
|
||||||
result := PrepareTicketDraftResult{
|
result := PrepareTicketDraftResult{
|
||||||
Priority: input.Priority,
|
|
||||||
Severity: input.Severity,
|
|
||||||
MissingFields: make([]string, 0, 2),
|
MissingFields: make([]string, 0, 2),
|
||||||
FollowUpQuestions: make([]string, 0, 2),
|
FollowUpQuestions: make([]string, 0, 2),
|
||||||
ConversationFacts: buildConversationFacts(conversation, messages),
|
ConversationFacts: buildConversationFacts(conversation, messages),
|
||||||
|
|||||||
@@ -119,8 +119,6 @@ func addRouter(app *iris.Application) {
|
|||||||
m.Party("/conversation").Handle(new(dashboard.ConversationController))
|
m.Party("/conversation").Handle(new(dashboard.ConversationController))
|
||||||
m.Party("/ticket").Handle(new(dashboard.TicketController))
|
m.Party("/ticket").Handle(new(dashboard.TicketController))
|
||||||
m.Party("/notification").Handle(new(dashboard.NotificationController))
|
m.Party("/notification").Handle(new(dashboard.NotificationController))
|
||||||
m.Party("/ticket-resolution-code").Handle(new(dashboard.TicketResolutionCodeController))
|
|
||||||
m.Party("/ticket-priority-config").Handle(new(dashboard.TicketPriorityConfigController))
|
|
||||||
m.Party("/quick-reply").Handle(new(dashboard.QuickReplyController))
|
m.Party("/quick-reply").Handle(new(dashboard.QuickReplyController))
|
||||||
m.Party("/channel").Handle(new(dashboard.ChannelController))
|
m.Party("/channel").Handle(new(dashboard.ChannelController))
|
||||||
m.Party("/agent").Handle(new(dashboard.AgentController))
|
m.Party("/agent").Handle(new(dashboard.AgentController))
|
||||||
|
|||||||
@@ -1,35 +1,23 @@
|
|||||||
package builders
|
package builders
|
||||||
|
|
||||||
import (
|
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"
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mlogclub/simple/sqls"
|
"cs-agent/internal/models"
|
||||||
|
"cs-agent/internal/pkg/dto/response"
|
||||||
|
"cs-agent/internal/pkg/utils"
|
||||||
|
"cs-agent/internal/services"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TicketBuildContext struct {
|
type TicketBuildContext struct {
|
||||||
TagsByTicketID map[int64][]models.Tag
|
TagsByTicketID map[int64][]models.Tag
|
||||||
Priorities map[int64]*models.TicketPriorityConfig
|
Users map[int64]*models.User
|
||||||
ResolutionCodes map[string]*models.TicketResolutionCode
|
Customers map[int64]*models.Customer
|
||||||
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 {
|
type TicketDetailBuildContext struct {
|
||||||
Users map[int64]*models.User
|
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 {
|
func BuildTicket(item *models.Ticket) *response.TicketResponse {
|
||||||
@@ -41,100 +29,43 @@ func BuildTicketWithContext(item *models.Ticket, ctx *TicketBuildContext) *respo
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
ret := &response.TicketResponse{
|
ret := &response.TicketResponse{
|
||||||
ID: item.ID,
|
ID: item.ID,
|
||||||
TicketNo: item.TicketNo,
|
TicketNo: item.TicketNo,
|
||||||
Title: item.Title,
|
Title: item.Title,
|
||||||
Description: item.Description,
|
Description: item.Description,
|
||||||
Source: item.Source,
|
Source: item.Source,
|
||||||
Channel: item.Channel,
|
Channel: item.Channel,
|
||||||
CustomerID: item.CustomerID,
|
CustomerID: item.CustomerID,
|
||||||
ConversationID: item.ConversationID,
|
ConversationID: item.ConversationID,
|
||||||
Type: item.Type,
|
Status: item.Status,
|
||||||
Priority: item.Priority,
|
CurrentAssigneeID: item.CurrentAssigneeID,
|
||||||
Severity: item.Severity,
|
CreatedBy: item.CreateUserID,
|
||||||
Status: item.Status,
|
CreatedByName: item.CreateUserName,
|
||||||
CurrentTeamID: item.CurrentTeamID,
|
HandledAt: utils.FormatTimePtr(item.HandledAt),
|
||||||
CurrentAssigneeID: item.CurrentAssigneeID,
|
CreatedAt: utils.FormatTime(item.CreatedAt),
|
||||||
PendingReason: item.PendingReason,
|
UpdatedAt: utils.FormatTime(item.UpdatedAt),
|
||||||
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 {
|
if ctx != nil && ctx.TagsByTicketID != nil {
|
||||||
ret.Tags = BuildTagResponses(ctx.TagsByTicketID[item.ID])
|
ret.Tags = BuildTagResponses(ctx.TagsByTicketID[item.ID])
|
||||||
} else {
|
} else {
|
||||||
ret.Tags = BuildTagResponses(services.TicketService.GetTags(item.ID))
|
ret.Tags = BuildTagResponses(services.TicketService.GetTags(item.ID))
|
||||||
}
|
}
|
||||||
if item.Priority > 0 && ctx != nil && ctx.Priorities != nil {
|
if item.CurrentAssigneeID > 0 {
|
||||||
if priority := ctx.Priorities[item.Priority]; priority != nil {
|
if ctx != nil && ctx.Users != nil {
|
||||||
ret.PriorityName = priority.Name
|
ret.CurrentAssigneeName = buildTicketUserDisplayName(ctx.Users[item.CurrentAssigneeID])
|
||||||
}
|
}
|
||||||
} else if item.Priority > 0 {
|
if ret.CurrentAssigneeName == "" {
|
||||||
if priority := services.TicketPriorityConfigService.Get(item.Priority); priority != nil {
|
ret.CurrentAssigneeName = buildTicketUserDisplayName(services.UserService.Get(item.CurrentAssigneeID))
|
||||||
ret.PriorityName = priority.Name
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if item.ResolutionCode != "" && ctx != nil && ctx.ResolutionCodes != nil {
|
if item.CustomerID > 0 {
|
||||||
if code := ctx.ResolutionCodes[item.ResolutionCode]; code != nil {
|
if ctx != nil && ctx.Customers != nil {
|
||||||
ret.ResolutionCodeName = code.Name
|
ret.Customer = BuildCustomer(ctx.Customers[item.CustomerID])
|
||||||
}
|
}
|
||||||
} else if item.ResolutionCode != "" {
|
if ret.Customer == nil {
|
||||||
if code := services.TicketResolutionCodeService.Take("code = ? AND status <> ?", item.ResolutionCode, enums.StatusDeleted); code != nil {
|
ret.Customer = BuildCustomer(services.CustomerService.Get(item.CustomerID))
|
||||||
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
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,343 +86,79 @@ func BuildTicketListWithContext(list []models.Ticket, ctx *TicketBuildContext) [
|
|||||||
return results
|
return results
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildTicketComment(item *models.TicketComment) *response.TicketCommentResponse {
|
func BuildTicketProgress(item *models.TicketProgress) *response.TicketProgressResponse {
|
||||||
return BuildTicketCommentWithContext(item, nil)
|
return BuildTicketProgressWithContext(item, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildTicketCommentWithContext(item *models.TicketComment, ctx *TicketDetailBuildContext) *response.TicketCommentResponse {
|
func BuildTicketProgressWithContext(item *models.TicketProgress, ctx *TicketDetailBuildContext) *response.TicketProgressResponse {
|
||||||
if item == nil {
|
if item == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
ret := &response.TicketCommentResponse{
|
ret := &response.TicketProgressResponse{
|
||||||
ID: item.ID,
|
ID: item.ID,
|
||||||
TicketID: item.TicketID,
|
TicketID: item.TicketID,
|
||||||
CommentType: item.CommentType,
|
Content: item.Content,
|
||||||
AuthorType: item.AuthorType,
|
AuthorID: item.AuthorID,
|
||||||
AuthorID: item.AuthorID,
|
CreatedAt: utils.FormatTime(item.CreatedAt),
|
||||||
ContentType: item.ContentType,
|
}
|
||||||
Content: item.Content,
|
if item.AuthorID > 0 {
|
||||||
Payload: item.Payload,
|
if ctx != nil && ctx.Users != nil {
|
||||||
CreatedAt: utils.FormatTime(item.CreatedAt),
|
ret.AuthorName = buildTicketUserDisplayName(ctx.Users[item.AuthorID])
|
||||||
|
}
|
||||||
|
if ret.AuthorName == "" {
|
||||||
|
ret.AuthorName = buildTicketUserDisplayName(services.UserService.Get(item.AuthorID))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ret.AuthorName = buildTicketOperatorName(item.AuthorType, item.AuthorID, ctx)
|
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func BuildTicketCommentList(list []models.TicketComment) []response.TicketCommentResponse {
|
func BuildTicketProgressListWithContext(list []models.TicketProgress, ctx *TicketDetailBuildContext) []response.TicketProgressResponse {
|
||||||
return BuildTicketCommentListWithContext(list, nil)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BuildTicketCommentListWithContext(list []models.TicketComment, ctx *TicketDetailBuildContext) []response.TicketCommentResponse {
|
|
||||||
if len(list) == 0 {
|
if len(list) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
results := make([]response.TicketCommentResponse, 0, len(list))
|
results := make([]response.TicketProgressResponse, 0, len(list))
|
||||||
for i := range list {
|
for i := range list {
|
||||||
if item := BuildTicketCommentWithContext(&list[i], ctx); item != nil {
|
if item := BuildTicketProgressWithContext(&list[i], ctx); item != nil {
|
||||||
results = append(results, *item)
|
results = append(results, *item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return results
|
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 {
|
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 {
|
if aggregate == nil || aggregate.Ticket == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if ctx == nil {
|
ctx := &TicketBuildContext{
|
||||||
users := make(map[int64]*models.User, len(aggregate.Users)+len(aggregate.OperatorUsers))
|
TagsByTicketID: map[int64][]models.Tag{aggregate.Ticket.ID: aggregate.Tags},
|
||||||
for id, item := range aggregate.Users {
|
Users: aggregate.Users,
|
||||||
users[id] = item
|
Customers: map[int64]*models.Customer{},
|
||||||
}
|
|
||||||
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 {
|
if aggregate.Customer != nil {
|
||||||
ret.Ticket.Customer = BuildCustomer(aggregate.Customer)
|
ctx.Customers[aggregate.Customer.ID] = aggregate.Customer
|
||||||
}
|
}
|
||||||
ret.Ticket.Tags = BuildTagResponses(aggregate.Tags)
|
ret := &response.TicketDetailResponse{
|
||||||
ret.Ticket.SLA = BuildTicketSLAList(aggregate.SLAs)
|
Ticket: *BuildTicketWithContext(aggregate.Ticket, ctx),
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
ret.Progresses = BuildTicketProgressListWithContext(aggregate.Progresses, &TicketDetailBuildContext{Users: aggregate.Users})
|
||||||
return ret
|
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 {
|
func BuildTicketSummary(summary *services.TicketSummaryAggregate) *response.TicketSummaryResponse {
|
||||||
if summary == nil {
|
if summary == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return &response.TicketSummaryResponse{
|
return &response.TicketSummaryResponse{
|
||||||
All: summary.All,
|
All: summary.All,
|
||||||
Mine: summary.Mine,
|
Pending: summary.Pending,
|
||||||
Watching: summary.Watching,
|
InProgress: summary.InProgress,
|
||||||
Collaboration: summary.Collaboration,
|
Done: summary.Done,
|
||||||
Participating: summary.Participating,
|
Unassigned: summary.Unassigned,
|
||||||
Mentioned: summary.Mentioned,
|
Mine: summary.Mine,
|
||||||
Unassigned: summary.Unassigned,
|
Stale: summary.Stale,
|
||||||
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 {
|
func BuildTicketView(item *models.TicketView) *response.TicketViewResponse {
|
||||||
if item == nil {
|
if item == nil {
|
||||||
return nil
|
return nil
|
||||||
@@ -529,39 +196,3 @@ func buildTicketUserDisplayName(user *models.User) string {
|
|||||||
}
|
}
|
||||||
return user.Username
|
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 ""
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
package builders
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"cs-agent/internal/models"
|
||||||
|
"cs-agent/internal/pkg/enums"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBuildLightweightTicket(t *testing.T) {
|
||||||
|
now := time.Date(2026, 5, 2, 12, 30, 0, 0, time.Local)
|
||||||
|
ticket := &models.Ticket{
|
||||||
|
ID: 12,
|
||||||
|
TicketNo: "TK202605020001",
|
||||||
|
Title: "登录失败",
|
||||||
|
Description: "客户反馈无法登录",
|
||||||
|
Source: enums.TicketSourceManual,
|
||||||
|
Channel: "web",
|
||||||
|
CustomerID: 3,
|
||||||
|
ConversationID: 4,
|
||||||
|
Status: enums.TicketStatusPending,
|
||||||
|
CurrentAssigneeID: 5,
|
||||||
|
AuditFields: models.AuditFields{
|
||||||
|
CreateUserID: 1,
|
||||||
|
CreateUserName: "admin",
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ctx := &TicketBuildContext{
|
||||||
|
TagsByTicketID: map[int64][]models.Tag{
|
||||||
|
12: {{ID: 8, Name: "登录", Status: enums.StatusOk}},
|
||||||
|
},
|
||||||
|
Users: map[int64]*models.User{
|
||||||
|
5: {ID: 5, Username: "agent", Nickname: "客服"},
|
||||||
|
},
|
||||||
|
Customers: map[int64]*models.Customer{
|
||||||
|
3: {ID: 3, Name: "客户"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
out := BuildTicketWithContext(ticket, ctx)
|
||||||
|
if out == nil {
|
||||||
|
t.Fatalf("expected ticket response")
|
||||||
|
}
|
||||||
|
if out.ID != ticket.ID || out.TicketNo != ticket.TicketNo || out.Status != ticket.Status {
|
||||||
|
t.Fatalf("unexpected ticket response: %+v", out)
|
||||||
|
}
|
||||||
|
if out.CurrentAssigneeName != "客服" {
|
||||||
|
t.Fatalf("expected assignee name, got %q", out.CurrentAssigneeName)
|
||||||
|
}
|
||||||
|
if len(out.Tags) != 1 || out.Tags[0].ID != 8 {
|
||||||
|
t.Fatalf("expected tag response, got %+v", out.Tags)
|
||||||
|
}
|
||||||
|
if out.Customer == nil || out.Customer.ID != 3 {
|
||||||
|
t.Fatalf("expected customer response, got %+v", out.Customer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBuildTicketProgress(t *testing.T) {
|
||||||
|
now := time.Date(2026, 5, 2, 12, 30, 0, 0, time.Local)
|
||||||
|
progress := &models.TicketProgress{
|
||||||
|
ID: 1,
|
||||||
|
TicketID: 2,
|
||||||
|
Content: "已联系客户",
|
||||||
|
AuthorID: 3,
|
||||||
|
CreatedAt: now,
|
||||||
|
}
|
||||||
|
ctx := &TicketDetailBuildContext{
|
||||||
|
Users: map[int64]*models.User{
|
||||||
|
3: {ID: 3, Username: "agent", Nickname: "客服"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
out := BuildTicketProgressWithContext(progress, ctx)
|
||||||
|
if out == nil {
|
||||||
|
t.Fatalf("expected progress response")
|
||||||
|
}
|
||||||
|
if out.ID != progress.ID || out.TicketID != progress.TicketID || out.Content != progress.Content {
|
||||||
|
t.Fatalf("unexpected progress response: %+v", out)
|
||||||
|
}
|
||||||
|
if out.AuthorName != "客服" {
|
||||||
|
t.Fatalf("expected author name, got %q", out.AuthorName)
|
||||||
|
}
|
||||||
|
if out.CreatedAt == "" {
|
||||||
|
t.Fatalf("expected createdAt to be formatted")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
package builders
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cs-agent/internal/models"
|
|
||||||
"cs-agent/internal/pkg/dto/response"
|
|
||||||
)
|
|
||||||
|
|
||||||
func BuildTicketResolutionCode(item *models.TicketResolutionCode) *response.TicketResolutionCodeResponse {
|
|
||||||
if item == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &response.TicketResolutionCodeResponse{
|
|
||||||
ID: item.ID,
|
|
||||||
Name: item.Name,
|
|
||||||
Code: item.Code,
|
|
||||||
SortNo: item.SortNo,
|
|
||||||
Status: item.Status,
|
|
||||||
Remark: item.Remark,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BuildTicketResolutionCodeList(list []models.TicketResolutionCode) []response.TicketResolutionCodeResponse {
|
|
||||||
if len(list) == 0 {
|
|
||||||
return make([]response.TicketResolutionCodeResponse, 0)
|
|
||||||
}
|
|
||||||
ret := make([]response.TicketResolutionCodeResponse, 0, len(list))
|
|
||||||
for i := range list {
|
|
||||||
if item := BuildTicketResolutionCode(&list[i]); item != nil {
|
|
||||||
ret = append(ret, *item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func BuildTicketPriorityConfig(item *models.TicketPriorityConfig) *response.TicketPriorityConfigResponse {
|
|
||||||
if item == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &response.TicketPriorityConfigResponse{
|
|
||||||
ID: item.ID,
|
|
||||||
Name: item.Name,
|
|
||||||
SortNo: item.SortNo,
|
|
||||||
FirstResponseMinutes: item.FirstResponseMinutes,
|
|
||||||
ResolutionMinutes: item.ResolutionMinutes,
|
|
||||||
Status: item.Status,
|
|
||||||
Remark: item.Remark,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BuildTicketPriorityConfigList(list []models.TicketPriorityConfig) []response.TicketPriorityConfigResponse {
|
|
||||||
if len(list) == 0 {
|
|
||||||
return make([]response.TicketPriorityConfigResponse, 0)
|
|
||||||
}
|
|
||||||
ret := make([]response.TicketPriorityConfigResponse, 0, len(list))
|
|
||||||
for i := range list {
|
|
||||||
if item := BuildTicketPriorityConfig(&list[i]); item != nil {
|
|
||||||
ret = append(ret, *item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
@@ -2,12 +2,10 @@ package dashboard
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"cs-agent/internal/builders"
|
"cs-agent/internal/builders"
|
||||||
"cs-agent/internal/pkg/constants"
|
"cs-agent/internal/pkg/constants"
|
||||||
"cs-agent/internal/pkg/dto/request"
|
"cs-agent/internal/pkg/dto/request"
|
||||||
"cs-agent/internal/pkg/enums"
|
|
||||||
"cs-agent/internal/services"
|
"cs-agent/internal/services"
|
||||||
|
|
||||||
"github.com/kataras/iris/v12"
|
"github.com/kataras/iris/v12"
|
||||||
@@ -26,13 +24,11 @@ func (c *TicketController) AnyList() *web.JsonResult {
|
|||||||
}
|
}
|
||||||
cnd := params.NewPagedSqlCnd(c.Ctx,
|
cnd := params.NewPagedSqlCnd(c.Ctx,
|
||||||
params.QueryFilter{ParamName: "status"},
|
params.QueryFilter{ParamName: "status"},
|
||||||
params.QueryFilter{ParamName: "priority"},
|
|
||||||
params.QueryFilter{ParamName: "severity"},
|
|
||||||
params.QueryFilter{ParamName: "currentTeamId"},
|
|
||||||
params.QueryFilter{ParamName: "currentAssigneeId"},
|
params.QueryFilter{ParamName: "currentAssigneeId"},
|
||||||
params.QueryFilter{ParamName: "customerId"},
|
params.QueryFilter{ParamName: "customerId"},
|
||||||
params.QueryFilter{ParamName: "conversationId"},
|
params.QueryFilter{ParamName: "conversationId"},
|
||||||
params.QueryFilter{ParamName: "source"},
|
params.QueryFilter{ParamName: "source"},
|
||||||
|
params.QueryFilter{ParamName: "channel"},
|
||||||
).Desc("updated_at").Desc("id")
|
).Desc("updated_at").Desc("id")
|
||||||
if keyword, _ := params.Get(c.Ctx, "keyword"); strings.TrimSpace(keyword) != "" {
|
if keyword, _ := params.Get(c.Ctx, "keyword"); strings.TrimSpace(keyword) != "" {
|
||||||
keyword = "%" + strings.TrimSpace(keyword) + "%"
|
keyword = "%" + strings.TrimSpace(keyword) + "%"
|
||||||
@@ -41,47 +37,21 @@ func (c *TicketController) AnyList() *web.JsonResult {
|
|||||||
if tagID, _ := params.GetInt64(c.Ctx, "tagId"); tagID > 0 {
|
if tagID, _ := params.GetInt64(c.Ctx, "tagId"); tagID > 0 {
|
||||||
cnd.Where("id IN (SELECT ticket_id FROM t_ticket_tag WHERE tag_id = ?)", tagID)
|
cnd.Where("id IN (SELECT ticket_id FROM t_ticket_tag WHERE tag_id = ?)", tagID)
|
||||||
}
|
}
|
||||||
if watching, _ := params.Get(c.Ctx, "watching"); watching == "1" || strings.EqualFold(watching, "true") {
|
|
||||||
cnd.Where("id IN (SELECT ticket_id FROM t_ticket_watcher WHERE user_id = ?)", operator.UserID)
|
|
||||||
}
|
|
||||||
if collaborating, _ := params.Get(c.Ctx, "collaborating"); collaborating == "1" || strings.EqualFold(collaborating, "true") {
|
|
||||||
cnd.Where("id IN (SELECT ticket_id FROM t_ticket_collaborator WHERE user_id = ?)", operator.UserID)
|
|
||||||
}
|
|
||||||
if collaboration, _ := params.Get(c.Ctx, "collaboration"); collaboration == "1" || strings.EqualFold(collaboration, "true") {
|
|
||||||
cnd.Where(
|
|
||||||
"(id IN (SELECT ticket_id FROM t_ticket_collaborator WHERE user_id = ?) OR id IN (SELECT ticket_id FROM t_ticket_mention WHERE mentioned_user_id = ?))",
|
|
||||||
operator.UserID,
|
|
||||||
operator.UserID,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if mentioned, _ := params.Get(c.Ctx, "mentioned"); mentioned == "1" || strings.EqualFold(mentioned, "true") {
|
|
||||||
cnd.Where("id IN (SELECT ticket_id FROM t_ticket_mention WHERE mentioned_user_id = ?)", operator.UserID)
|
|
||||||
}
|
|
||||||
if mine, _ := params.Get(c.Ctx, "mine"); mine == "1" || strings.EqualFold(mine, "true") {
|
if mine, _ := params.Get(c.Ctx, "mine"); mine == "1" || strings.EqualFold(mine, "true") {
|
||||||
cnd.Eq("current_assignee_id", operator.UserID)
|
cnd.Eq("current_assignee_id", operator.UserID)
|
||||||
}
|
}
|
||||||
if unassigned, _ := params.Get(c.Ctx, "unassigned"); unassigned == "1" || strings.EqualFold(unassigned, "true") {
|
if unassigned, _ := params.Get(c.Ctx, "unassigned"); unassigned == "1" || strings.EqualFold(unassigned, "true") {
|
||||||
cnd.Eq("current_assignee_id", 0)
|
cnd.Eq("current_assignee_id", 0)
|
||||||
}
|
}
|
||||||
if overdue, _ := params.Get(c.Ctx, "overdue"); overdue == "1" || strings.EqualFold(overdue, "true") {
|
|
||||||
cnd.In("status", []string{"new", "open", "pending_customer", "pending_internal"})
|
|
||||||
cnd.Where("resolve_deadline_at IS NOT NULL")
|
|
||||||
cnd.Where("resolve_deadline_at < ?", time.Now())
|
|
||||||
}
|
|
||||||
aggregate, err := services.TicketService.FindPageAggregateByCnd(cnd, operator.UserID)
|
aggregate, err := services.TicketService.FindPageAggregateByCnd(cnd, operator.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return web.JsonError(err)
|
return web.JsonError(err)
|
||||||
}
|
}
|
||||||
return web.JsonData(&web.PageResult{
|
return web.JsonData(&web.PageResult{
|
||||||
Results: builders.BuildTicketListWithContext(aggregate.List, &builders.TicketBuildContext{
|
Results: builders.BuildTicketListWithContext(aggregate.List, &builders.TicketBuildContext{
|
||||||
TagsByTicketID: aggregate.TagsByTicketID,
|
TagsByTicketID: aggregate.TagsByTicketID,
|
||||||
Priorities: aggregate.Priorities,
|
Users: aggregate.Users,
|
||||||
ResolutionCodes: aggregate.ResolutionCodes,
|
Customers: aggregate.Customers,
|
||||||
Users: aggregate.Users,
|
|
||||||
Teams: aggregate.Teams,
|
|
||||||
Customers: aggregate.Customers,
|
|
||||||
SLAByTicketID: aggregate.SLAByTicketID,
|
|
||||||
WatchedTicketIDs: aggregate.WatchedTicketIDs,
|
|
||||||
}),
|
}),
|
||||||
Page: aggregate.Paging,
|
Page: aggregate.Paging,
|
||||||
})
|
})
|
||||||
@@ -95,44 +65,6 @@ func (c *TicketController) AnySummary() *web.JsonResult {
|
|||||||
return web.JsonData(builders.BuildTicketSummary(services.TicketService.GetSummary(operator)))
|
return web.JsonData(builders.BuildTicketSummary(services.TicketService.GetSummary(operator)))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TicketController) AnyRisk_overview() *web.JsonResult {
|
|
||||||
if _, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketView); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
teamID, _ := params.GetInt64(c.Ctx, "currentTeamId")
|
|
||||||
riskWindowMins, _ := params.GetInt(c.Ctx, "riskWindowMins")
|
|
||||||
return web.JsonData(builders.BuildTicketRiskOverview(services.TicketService.GetRiskOverview(teamID, riskWindowMins)))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TicketController) AnyRisk_list() *web.JsonResult {
|
|
||||||
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketView)
|
|
||||||
if err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
riskType, _ := params.Get(c.Ctx, "riskType")
|
|
||||||
teamID, _ := params.GetInt64(c.Ctx, "currentTeamId")
|
|
||||||
riskWindowMins, _ := params.GetInt(c.Ctx, "riskWindowMins")
|
|
||||||
page, _ := params.GetInt(c.Ctx, "page")
|
|
||||||
limit, _ := params.GetInt(c.Ctx, "limit")
|
|
||||||
aggregate, err := services.TicketService.GetRiskPageAggregate(riskType, teamID, riskWindowMins, page, limit, operator.UserID)
|
|
||||||
if err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
return web.JsonData(&web.PageResult{
|
|
||||||
Results: builders.BuildTicketListWithContext(aggregate.List, &builders.TicketBuildContext{
|
|
||||||
TagsByTicketID: aggregate.TagsByTicketID,
|
|
||||||
Priorities: aggregate.Priorities,
|
|
||||||
ResolutionCodes: aggregate.ResolutionCodes,
|
|
||||||
Users: aggregate.Users,
|
|
||||||
Teams: aggregate.Teams,
|
|
||||||
Customers: aggregate.Customers,
|
|
||||||
SLAByTicketID: aggregate.SLAByTicketID,
|
|
||||||
WatchedTicketIDs: aggregate.WatchedTicketIDs,
|
|
||||||
}),
|
|
||||||
Page: aggregate.Paging,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TicketController) AnyView_list() *web.JsonResult {
|
func (c *TicketController) AnyView_list() *web.JsonResult {
|
||||||
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketView)
|
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketView)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -230,21 +162,6 @@ func (c *TicketController) PostUpdate() *web.JsonResult {
|
|||||||
return web.JsonSuccess()
|
return web.JsonSuccess()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TicketController) PostLink_customer() *web.JsonResult {
|
|
||||||
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketUpdate)
|
|
||||||
if err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
req := request.LinkTicketCustomerRequest{}
|
|
||||||
if err := params.ReadJSON(c.Ctx, &req); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
if err := services.TicketService.LinkTicketCustomer(req.TicketID, req.CustomerID, operator); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
return web.JsonSuccess()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TicketController) PostAssign() *web.JsonResult {
|
func (c *TicketController) PostAssign() *web.JsonResult {
|
||||||
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketAssign)
|
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketAssign)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -290,212 +207,18 @@ func (c *TicketController) PostChange_status() *web.JsonResult {
|
|||||||
return web.JsonSuccess()
|
return web.JsonSuccess()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *TicketController) PostBatch_change_status() *web.JsonResult {
|
func (c *TicketController) PostAdd_progress() *web.JsonResult {
|
||||||
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketChangeStatus)
|
|
||||||
if err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
req := request.BatchChangeTicketStatusRequest{}
|
|
||||||
if err := params.ReadJSON(c.Ctx, &req); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
if err := services.TicketService.BatchChangeStatus(req, operator); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
return web.JsonSuccess()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TicketController) PostReply() *web.JsonResult {
|
|
||||||
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketReply)
|
|
||||||
if err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
req := request.ReplyTicketRequest{}
|
|
||||||
if err := params.ReadJSON(c.Ctx, &req); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
item, err := services.TicketService.ReplyTicket(req, operator)
|
|
||||||
if err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
return web.JsonData(builders.BuildTicketComment(item))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TicketController) PostInternal_note() *web.JsonResult {
|
|
||||||
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketReply)
|
|
||||||
if err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
req := request.InternalNoteRequest{}
|
|
||||||
if err := params.ReadJSON(c.Ctx, &req); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
item, err := services.TicketService.AddInternalNote(req, operator)
|
|
||||||
if err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
return web.JsonData(builders.BuildTicketComment(item))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TicketController) PostClose() *web.JsonResult {
|
|
||||||
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketClose)
|
|
||||||
if err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
req := request.CloseTicketRequest{}
|
|
||||||
if err := params.ReadJSON(c.Ctx, &req); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
if err := services.TicketService.CloseTicket(req, operator); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
return web.JsonSuccess()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TicketController) PostReopen() *web.JsonResult {
|
|
||||||
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketReopen)
|
|
||||||
if err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
req := request.ReopenTicketRequest{}
|
|
||||||
if err := params.ReadJSON(c.Ctx, &req); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
if err := services.TicketService.ReopenTicket(req, operator); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
return web.JsonSuccess()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TicketController) PostWatch() *web.JsonResult {
|
|
||||||
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketView)
|
|
||||||
if err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
req := request.WatchTicketRequest{}
|
|
||||||
if err := params.ReadJSON(c.Ctx, &req); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
if err := services.TicketService.WatchTicket(req.TicketID, operator); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
return web.JsonSuccess()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TicketController) PostUnwatch() *web.JsonResult {
|
|
||||||
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketView)
|
|
||||||
if err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
req := request.WatchTicketRequest{}
|
|
||||||
if err := params.ReadJSON(c.Ctx, &req); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
if err := services.TicketService.UnwatchTicket(req.TicketID, operator); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
return web.JsonSuccess()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TicketController) PostBatch_watch() *web.JsonResult {
|
|
||||||
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketView)
|
|
||||||
if err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
req := request.BatchWatchTicketRequest{}
|
|
||||||
if err := params.ReadJSON(c.Ctx, &req); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
if err := services.TicketService.BatchWatchTickets(req, operator); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
return web.JsonSuccess()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TicketController) PostAdd_relation() *web.JsonResult {
|
|
||||||
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketUpdate)
|
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketUpdate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return web.JsonError(err)
|
return web.JsonError(err)
|
||||||
}
|
}
|
||||||
req := request.AddTicketRelationRequest{}
|
req := request.CreateTicketProgressRequest{}
|
||||||
if err := params.ReadJSON(c.Ctx, &req); err != nil {
|
if err := params.ReadJSON(c.Ctx, &req); err != nil {
|
||||||
return web.JsonError(err)
|
return web.JsonError(err)
|
||||||
}
|
}
|
||||||
relatedTicketID := req.RelatedTicketID
|
item, err := services.TicketService.AddProgress(req, operator)
|
||||||
if relatedTicketID <= 0 && strings.TrimSpace(req.RelatedTicketNo) != "" {
|
|
||||||
if relatedTicket := services.TicketService.Take("ticket_no = ?", strings.TrimSpace(req.RelatedTicketNo)); relatedTicket != nil {
|
|
||||||
relatedTicketID = relatedTicket.ID
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := services.TicketRelationService.AddRelation(req.TicketID, relatedTicketID, enums.TicketRelationType(strings.TrimSpace(req.RelationType)), operator); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
return web.JsonSuccess()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TicketController) PostDelete_relation() *web.JsonResult {
|
|
||||||
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketUpdate)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return web.JsonError(err)
|
return web.JsonError(err)
|
||||||
}
|
}
|
||||||
req := request.DeleteTicketRelationRequest{}
|
return web.JsonData(builders.BuildTicketProgress(item))
|
||||||
if err := params.ReadJSON(c.Ctx, &req); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
if err := services.TicketRelationService.DeleteRelation(req.TicketID, req.RelationID, operator); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
return web.JsonSuccess()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TicketController) PostAdd_collaborator() *web.JsonResult {
|
|
||||||
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketUpdate)
|
|
||||||
if err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
req := request.AddTicketCollaboratorRequest{}
|
|
||||||
if err := params.ReadJSON(c.Ctx, &req); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
if err := services.TicketService.AddCollaborator(req.TicketID, req.UserID, operator); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
return web.JsonSuccess()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TicketController) PostDelete_collaborator() *web.JsonResult {
|
|
||||||
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketUpdate)
|
|
||||||
if err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
req := request.DeleteTicketCollaboratorRequest{}
|
|
||||||
if err := params.ReadJSON(c.Ctx, &req); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
if err := services.TicketService.RemoveCollaborator(req.TicketID, req.CollaboratorID, operator); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
return web.JsonSuccess()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TicketController) AnyComment_list() *web.JsonResult {
|
|
||||||
if _, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketView); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
ticketID, _ := params.GetInt64(c.Ctx, "ticketId")
|
|
||||||
if ticketID <= 0 {
|
|
||||||
return web.JsonData(&web.PageResult{Results: []any{}, Page: params.GetPaging(c.Ctx)})
|
|
||||||
}
|
|
||||||
cnd := params.NewPagedSqlCnd(c.Ctx, params.QueryFilter{ParamName: "ticketId"}).Asc("id")
|
|
||||||
results, paging := services.TicketCommentService.FindPageByCnd(cnd)
|
|
||||||
return web.JsonData(&web.PageResult{Results: builders.BuildTicketCommentList(results), Page: paging})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TicketController) AnyEvent_list() *web.JsonResult {
|
|
||||||
if _, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketView); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
cnd := params.NewPagedSqlCnd(c.Ctx, params.QueryFilter{ParamName: "ticketId"}).Desc("id")
|
|
||||||
list, paging := services.TicketEventLogService.FindPageByCnd(cnd)
|
|
||||||
return web.JsonData(&web.PageResult{Results: builders.BuildTicketEventLogList(list), Page: paging})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
package dashboard
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cs-agent/internal/builders"
|
|
||||||
"cs-agent/internal/pkg/constants"
|
|
||||||
"cs-agent/internal/pkg/dto/request"
|
|
||||||
"cs-agent/internal/pkg/enums"
|
|
||||||
"cs-agent/internal/services"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"github.com/kataras/iris/v12"
|
|
||||||
"github.com/mlogclub/simple/sqls"
|
|
||||||
"github.com/mlogclub/simple/web"
|
|
||||||
"github.com/mlogclub/simple/web/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TicketPriorityConfigController struct{ Ctx iris.Context }
|
|
||||||
|
|
||||||
func (c *TicketPriorityConfigController) AnyList() *web.JsonResult {
|
|
||||||
if _, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketPriorityConfigView); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
cnd := sqls.NewCnd().Asc("sort_no").Asc("id")
|
|
||||||
if status, ok := params.Get(c.Ctx, "status"); ok && strings.TrimSpace(status) != "" {
|
|
||||||
cnd.Eq("status", status)
|
|
||||||
} else {
|
|
||||||
cnd.Where("status <> ?", enums.StatusDeleted)
|
|
||||||
}
|
|
||||||
if name, ok := params.Get(c.Ctx, "name"); ok && strings.TrimSpace(name) != "" {
|
|
||||||
cnd.Where("name LIKE ?", "%"+strings.TrimSpace(name)+"%")
|
|
||||||
}
|
|
||||||
list := services.TicketPriorityConfigService.Find(cnd)
|
|
||||||
return web.JsonData(builders.BuildTicketPriorityConfigList(list))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TicketPriorityConfigController) GetList_all() *web.JsonResult {
|
|
||||||
if _, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketPriorityConfigView); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
list := services.TicketPriorityConfigService.Find(sqls.NewCnd().Eq("status", enums.StatusOk).Asc("sort_no").Asc("id"))
|
|
||||||
return web.JsonData(builders.BuildTicketPriorityConfigList(list))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TicketPriorityConfigController) PostCreate() *web.JsonResult {
|
|
||||||
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketPriorityConfigCreate)
|
|
||||||
if err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
req := request.CreateTicketPriorityConfigRequest{}
|
|
||||||
if err := params.ReadJSON(c.Ctx, &req); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
item, err := services.TicketPriorityConfigService.CreateTicketPriorityConfig(req, operator)
|
|
||||||
if err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
return web.JsonData(builders.BuildTicketPriorityConfig(item))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TicketPriorityConfigController) PostUpdate() *web.JsonResult {
|
|
||||||
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketPriorityConfigUpdate)
|
|
||||||
if err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
req := request.UpdateTicketPriorityConfigRequest{}
|
|
||||||
if err := params.ReadJSON(c.Ctx, &req); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
if err := services.TicketPriorityConfigService.UpdateTicketPriorityConfig(req, operator); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
return web.JsonSuccess()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TicketPriorityConfigController) PostUpdate_sort() *web.JsonResult {
|
|
||||||
if _, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketPriorityConfigUpdate); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
var ids []int64
|
|
||||||
if err := c.Ctx.ReadJSON(&ids); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
if err := services.TicketPriorityConfigService.UpdateSort(ids); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
return web.JsonSuccess()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TicketPriorityConfigController) PostDelete() *web.JsonResult {
|
|
||||||
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketPriorityConfigDelete)
|
|
||||||
if err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
req := request.DeleteTicketPriorityConfigRequest{}
|
|
||||||
if err := params.ReadJSON(c.Ctx, &req); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
if err := services.TicketPriorityConfigService.DeleteTicketPriorityConfig(req.ID, operator); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
return web.JsonSuccess()
|
|
||||||
}
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
package dashboard
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cs-agent/internal/builders"
|
|
||||||
"cs-agent/internal/pkg/constants"
|
|
||||||
"cs-agent/internal/pkg/dto/request"
|
|
||||||
"cs-agent/internal/pkg/enums"
|
|
||||||
"cs-agent/internal/services"
|
|
||||||
|
|
||||||
"github.com/kataras/iris/v12"
|
|
||||||
"github.com/mlogclub/simple/sqls"
|
|
||||||
"github.com/mlogclub/simple/web"
|
|
||||||
"github.com/mlogclub/simple/web/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TicketResolutionCodeController struct{ Ctx iris.Context }
|
|
||||||
|
|
||||||
func (c *TicketResolutionCodeController) AnyList() *web.JsonResult {
|
|
||||||
if _, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketResolutionCodeView); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
cnd := params.NewPagedSqlCnd(c.Ctx,
|
|
||||||
params.QueryFilter{ParamName: "status"},
|
|
||||||
params.QueryFilter{ParamName: "name", Op: params.Like},
|
|
||||||
).Asc("sort_no").Desc("id")
|
|
||||||
if _, ok := params.Get(c.Ctx, "status"); !ok {
|
|
||||||
cnd.Where("status <> ?", enums.StatusDeleted)
|
|
||||||
}
|
|
||||||
list, paging := services.TicketResolutionCodeService.FindPageByCnd(cnd)
|
|
||||||
return web.JsonData(&web.PageResult{Results: builders.BuildTicketResolutionCodeList(list), Page: paging})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TicketResolutionCodeController) GetList_all() *web.JsonResult {
|
|
||||||
if _, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketResolutionCodeView); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
list := services.TicketResolutionCodeService.Find(sqls.NewCnd().Eq("status", enums.StatusOk).Asc("sort_no").Desc("id"))
|
|
||||||
return web.JsonData(builders.BuildTicketResolutionCodeList(list))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TicketResolutionCodeController) PostCreate() *web.JsonResult {
|
|
||||||
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketResolutionCodeCreate)
|
|
||||||
if err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
req := request.CreateTicketResolutionCodeRequest{}
|
|
||||||
if err := params.ReadJSON(c.Ctx, &req); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
item, err := services.TicketResolutionCodeService.CreateTicketResolutionCode(req, operator)
|
|
||||||
if err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
return web.JsonData(builders.BuildTicketResolutionCode(item))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TicketResolutionCodeController) PostUpdate() *web.JsonResult {
|
|
||||||
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketResolutionCodeUpdate)
|
|
||||||
if err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
req := request.UpdateTicketResolutionCodeRequest{}
|
|
||||||
if err := params.ReadJSON(c.Ctx, &req); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
if err := services.TicketResolutionCodeService.UpdateTicketResolutionCode(req, operator); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
return web.JsonSuccess()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *TicketResolutionCodeController) PostDelete() *web.JsonResult {
|
|
||||||
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketResolutionCodeDelete)
|
|
||||||
if err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
req := request.DeleteTicketResolutionCodeRequest{}
|
|
||||||
if err := params.ReadJSON(c.Ctx, &req); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
if err := services.TicketResolutionCodeService.DeleteTicketResolutionCode(req.ID, operator); err != nil {
|
|
||||||
return web.JsonError(err)
|
|
||||||
}
|
|
||||||
return web.JsonSuccess()
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package migration
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"cs-agent/internal/models"
|
|
||||||
"cs-agent/internal/pkg/constants"
|
|
||||||
"cs-agent/internal/pkg/enums"
|
|
||||||
|
|
||||||
"github.com/mlogclub/simple/sqls"
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
register(3, "init ticket priority configs", func() error {
|
|
||||||
return sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
|
||||||
var total int64
|
|
||||||
if err := ctx.Tx.Model(&models.TicketPriorityConfig{}).Where("status <> ?", enums.StatusDeleted).Count(&total).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if total == 0 {
|
|
||||||
now := time.Now()
|
|
||||||
items := []*models.TicketPriorityConfig{
|
|
||||||
{Name: "普通", SortNo: 10, FirstResponseMinutes: 30, ResolutionMinutes: 1440, Status: enums.StatusOk},
|
|
||||||
{Name: "高", SortNo: 20, FirstResponseMinutes: 10, ResolutionMinutes: 240, Status: enums.StatusOk},
|
|
||||||
{Name: "紧急", SortNo: 30, FirstResponseMinutes: 5, ResolutionMinutes: 120, Status: enums.StatusOk},
|
|
||||||
}
|
|
||||||
for _, item := range items {
|
|
||||||
item.AuditFields = models.AuditFields{
|
|
||||||
CreatedAt: now,
|
|
||||||
CreateUserID: constants.SystemAuditUserID,
|
|
||||||
CreateUserName: constants.SystemAuditUserName,
|
|
||||||
UpdatedAt: now,
|
|
||||||
UpdateUserID: constants.SystemAuditUserID,
|
|
||||||
UpdateUserName: constants.SystemAuditUserName,
|
|
||||||
}
|
|
||||||
if err := ctx.Tx.Create(item).Error; err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
+20
-135
@@ -8,8 +8,6 @@ import (
|
|||||||
// Models 注册所有需要迁移和代码生成的模型。
|
// Models 注册所有需要迁移和代码生成的模型。
|
||||||
var Models = []any{
|
var Models = []any{
|
||||||
&Migration{},
|
&Migration{},
|
||||||
&TicketNoSequence{},
|
|
||||||
&TicketView{},
|
|
||||||
&User{},
|
&User{},
|
||||||
&UserIdentity{},
|
&UserIdentity{},
|
||||||
&Company{},
|
&Company{},
|
||||||
@@ -39,15 +37,9 @@ var Models = []any{
|
|||||||
&ConversationEventLog{},
|
&ConversationEventLog{},
|
||||||
&Ticket{},
|
&Ticket{},
|
||||||
&TicketTag{},
|
&TicketTag{},
|
||||||
&TicketResolutionCode{},
|
&TicketProgress{},
|
||||||
&TicketPriorityConfig{},
|
&TicketView{},
|
||||||
&TicketComment{},
|
&TicketNoSequence{},
|
||||||
&TicketWatcher{},
|
|
||||||
&TicketCollaborator{},
|
|
||||||
&TicketMention{},
|
|
||||||
&TicketEventLog{},
|
|
||||||
&TicketSLARecord{},
|
|
||||||
&TicketRelation{},
|
|
||||||
&Notification{},
|
&Notification{},
|
||||||
&AIAgent{},
|
&AIAgent{},
|
||||||
&Channel{},
|
&Channel{},
|
||||||
@@ -568,35 +560,19 @@ type ConversationEventLog struct {
|
|||||||
CreatedAt time.Time `gorm:"type:datetime;not null;index"`
|
CreatedAt time.Time `gorm:"type:datetime;not null;index"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ticket 客服工单主档。
|
// Ticket 客服问题记录。
|
||||||
type Ticket struct {
|
type Ticket struct {
|
||||||
ID int64 `gorm:"primaryKey;autoIncrement"`
|
ID int64 `gorm:"primaryKey;autoIncrement"`
|
||||||
TicketNo string `gorm:"type:varchar(64);not null;default:'';uniqueIndex"`
|
TicketNo string `gorm:"type:varchar(64);not null;default:'';uniqueIndex"`
|
||||||
Title string `gorm:"type:varchar(255);not null;default:'';index"`
|
Title string `gorm:"type:varchar(255);not null;default:'';index"`
|
||||||
Description string `gorm:"type:text"`
|
Description string `gorm:"type:text"`
|
||||||
Source enums.TicketSource `gorm:"type:varchar(50);not null;default:'';index"`
|
Source enums.TicketSource `gorm:"type:varchar(50);not null;default:'';index"`
|
||||||
Channel string `gorm:"type:varchar(50);not null;default:'';index"`
|
Channel string `gorm:"type:varchar(50);not null;default:'';index"`
|
||||||
CustomerID int64 `gorm:"type:bigint;not null;default:0;index"`
|
CustomerID int64 `gorm:"type:bigint;not null;default:0;index"`
|
||||||
ConversationID int64 `gorm:"type:bigint;not null;default:0;index"`
|
ConversationID int64 `gorm:"type:bigint;not null;default:0;index"`
|
||||||
Type string `gorm:"type:varchar(50);not null;default:'';index"`
|
Status enums.TicketStatus `gorm:"type:varchar(50);not null;default:'pending';index"`
|
||||||
Priority int64 `gorm:"type:bigint;not null;default:0;index"`
|
CurrentAssigneeID int64 `gorm:"type:bigint;not null;default:0;index"`
|
||||||
Severity enums.TicketSeverity `gorm:"type:int;not null;default:1;index"`
|
HandledAt *time.Time `gorm:"type:datetime;index"`
|
||||||
Status enums.TicketStatus `gorm:"type:varchar(50);not null;default:'new';index"`
|
|
||||||
CurrentTeamID int64 `gorm:"type:bigint;not null;default:0;index"`
|
|
||||||
CurrentAssigneeID int64 `gorm:"type:bigint;not null;default:0;index"`
|
|
||||||
PendingReason string `gorm:"type:varchar(255);not null;default:''"`
|
|
||||||
CloseReason string `gorm:"type:varchar(255);not null;default:''"`
|
|
||||||
ResolutionCode string `gorm:"type:varchar(100);not null;default:''"`
|
|
||||||
ResolutionSummary string `gorm:"type:text"`
|
|
||||||
FirstResponseAt *time.Time `gorm:"type:datetime;index"`
|
|
||||||
ResolvedAt *time.Time `gorm:"type:datetime;index"`
|
|
||||||
ClosedAt *time.Time `gorm:"type:datetime;index"`
|
|
||||||
DueAt *time.Time `gorm:"type:datetime;index"`
|
|
||||||
NextReplyDeadlineAt *time.Time `gorm:"type:datetime;index"`
|
|
||||||
ResolveDeadlineAt *time.Time `gorm:"type:datetime;index"`
|
|
||||||
ReopenedCount int `gorm:"type:int;not null;default:0"`
|
|
||||||
CustomFieldsJSON string `gorm:"type:text"`
|
|
||||||
ExtraJSON string `gorm:"type:text"`
|
|
||||||
AuditFields
|
AuditFields
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -608,106 +584,15 @@ type TicketTag struct {
|
|||||||
AuditFields
|
AuditFields
|
||||||
}
|
}
|
||||||
|
|
||||||
// TicketResolutionCode 工单解决码。
|
// TicketProgress 工单处理进展。
|
||||||
type TicketResolutionCode struct {
|
type TicketProgress struct {
|
||||||
ID int64 `gorm:"primaryKey;autoIncrement"`
|
|
||||||
Name string `gorm:"type:varchar(100);not null;default:'';index"`
|
|
||||||
Code string `gorm:"type:varchar(100);not null;default:'';uniqueIndex"`
|
|
||||||
SortNo int `gorm:"type:int;not null;default:0;index"`
|
|
||||||
Status enums.Status `gorm:"type:int;not null;default:0;index"`
|
|
||||||
Remark string `gorm:"type:text"`
|
|
||||||
AuditFields
|
|
||||||
}
|
|
||||||
|
|
||||||
// TicketPriorityConfig 工单优先级配置。
|
|
||||||
type TicketPriorityConfig struct {
|
|
||||||
ID int64 `gorm:"primaryKey;autoIncrement"`
|
|
||||||
Name string `gorm:"type:varchar(100);not null;default:'';uniqueIndex"`
|
|
||||||
SortNo int `gorm:"type:int;not null;default:0;index"`
|
|
||||||
FirstResponseMinutes int `gorm:"type:int;not null;default:0"`
|
|
||||||
ResolutionMinutes int `gorm:"type:int;not null;default:0"`
|
|
||||||
Status enums.Status `gorm:"type:int;not null;default:0;index"`
|
|
||||||
Remark string `gorm:"type:text"`
|
|
||||||
AuditFields
|
|
||||||
}
|
|
||||||
|
|
||||||
// TicketComment 工单评论。
|
|
||||||
type TicketComment struct {
|
|
||||||
ID int64 `gorm:"primaryKey;autoIncrement"`
|
|
||||||
TicketID int64 `gorm:"type:bigint;not null;index"`
|
|
||||||
CommentType enums.TicketCommentType `gorm:"type:varchar(50);not null;default:'';index"`
|
|
||||||
AuthorType enums.IMSenderType `gorm:"type:varchar(30);not null;default:'';index"`
|
|
||||||
AuthorID int64 `gorm:"type:bigint;not null;default:0;index"`
|
|
||||||
ContentType string `gorm:"type:varchar(30);not null;default:''"`
|
|
||||||
Content string `gorm:"type:text"`
|
|
||||||
Payload string `gorm:"type:text"`
|
|
||||||
CreatedAt time.Time `gorm:"type:datetime;not null;index"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TicketWatcher 工单关注人。
|
|
||||||
type TicketWatcher struct {
|
|
||||||
ID int64 `gorm:"primaryKey;autoIncrement"`
|
ID int64 `gorm:"primaryKey;autoIncrement"`
|
||||||
TicketID int64 `gorm:"type:bigint;not null;index;uniqueIndex:uk_ticket_watcher"`
|
TicketID int64 `gorm:"type:bigint;not null;index"`
|
||||||
UserID int64 `gorm:"type:bigint;not null;index;uniqueIndex:uk_ticket_watcher"`
|
Content string `gorm:"type:text"`
|
||||||
|
AuthorID int64 `gorm:"type:bigint;not null;default:0;index"`
|
||||||
CreatedAt time.Time `gorm:"type:datetime;not null;index"`
|
CreatedAt time.Time `gorm:"type:datetime;not null;index"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// TicketCollaborator 工单协作人。
|
|
||||||
type TicketCollaborator struct {
|
|
||||||
ID int64 `gorm:"primaryKey;autoIncrement"`
|
|
||||||
TicketID int64 `gorm:"type:bigint;not null;index;uniqueIndex:uk_ticket_collaborator"`
|
|
||||||
UserID int64 `gorm:"type:bigint;not null;index;uniqueIndex:uk_ticket_collaborator"`
|
|
||||||
CreatedAt time.Time `gorm:"type:datetime;not null;index"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TicketMention 工单提及记录。
|
|
||||||
type TicketMention struct {
|
|
||||||
ID int64 `gorm:"primaryKey;autoIncrement"`
|
|
||||||
TicketID int64 `gorm:"type:bigint;not null;index;uniqueIndex:uk_ticket_mention"`
|
|
||||||
CommentID int64 `gorm:"type:bigint;not null;index;uniqueIndex:uk_ticket_mention"`
|
|
||||||
MentionedUserID int64 `gorm:"type:bigint;not null;index;uniqueIndex:uk_ticket_mention"`
|
|
||||||
CreatedAt time.Time `gorm:"type:datetime;not null;index"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TicketEventLog 工单事件日志。
|
|
||||||
type TicketEventLog struct {
|
|
||||||
ID int64 `gorm:"primaryKey;autoIncrement"`
|
|
||||||
TicketID int64 `gorm:"type:bigint;not null;index"`
|
|
||||||
EventType enums.TicketEventType `gorm:"type:varchar(50);not null;default:'';index"`
|
|
||||||
OperatorType enums.IMSenderType `gorm:"type:varchar(30);not null;default:'';index"`
|
|
||||||
OperatorID int64 `gorm:"type:bigint;not null;default:0;index"`
|
|
||||||
OldValue string `gorm:"type:text"`
|
|
||||||
NewValue string `gorm:"type:text"`
|
|
||||||
Content string `gorm:"type:text"`
|
|
||||||
Payload string `gorm:"type:text"`
|
|
||||||
CreatedAt time.Time `gorm:"type:datetime;not null;index"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TicketSLARecord 工单 SLA 记录。
|
|
||||||
type TicketSLARecord struct {
|
|
||||||
ID int64 `gorm:"primaryKey;autoIncrement"`
|
|
||||||
TicketID int64 `gorm:"type:bigint;not null;index"`
|
|
||||||
SLAType enums.TicketSLAType `gorm:"type:varchar(50);not null;default:'';index"`
|
|
||||||
TargetMinutes int `gorm:"type:int;not null;default:0"`
|
|
||||||
Status enums.TicketSLAStatus `gorm:"type:varchar(30);not null;default:'';index"`
|
|
||||||
StartedAt *time.Time `gorm:"type:datetime;index"`
|
|
||||||
PausedAt *time.Time `gorm:"type:datetime;index"`
|
|
||||||
StoppedAt *time.Time `gorm:"type:datetime;index"`
|
|
||||||
BreachedAt *time.Time `gorm:"type:datetime;index"`
|
|
||||||
ElapsedMin int `gorm:"type:int;not null;default:0"`
|
|
||||||
CreatedAt time.Time `gorm:"type:datetime;not null;index"`
|
|
||||||
UpdatedAt time.Time `gorm:"type:datetime;not null;index"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// TicketRelation 工单关联关系。
|
|
||||||
type TicketRelation struct {
|
|
||||||
ID int64 `gorm:"primaryKey;autoIncrement"`
|
|
||||||
TicketID int64 `gorm:"type:bigint;not null;index"`
|
|
||||||
RelatedTicketID int64 `gorm:"type:bigint;not null;index"`
|
|
||||||
RelationType enums.TicketRelationType `gorm:"type:varchar(30);not null;default:'';index"`
|
|
||||||
CreatedAt time.Time `gorm:"type:datetime;not null;index"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// AgentProfile 客服档案。
|
// AgentProfile 客服档案。
|
||||||
type AgentProfile struct {
|
type AgentProfile struct {
|
||||||
ID int64 `gorm:"primaryKey;autoIncrement"` // ID 为客服档案主键。
|
ID int64 `gorm:"primaryKey;autoIncrement"` // ID 为客服档案主键。
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
package request
|
|
||||||
|
|
||||||
import "cs-agent/internal/pkg/enums"
|
|
||||||
|
|
||||||
type CreateTicketResolutionCodeRequest struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
Code string `json:"code"`
|
|
||||||
SortNo int `json:"sortNo"`
|
|
||||||
Status enums.Status `json:"status"`
|
|
||||||
Remark string `json:"remark"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateTicketResolutionCodeRequest struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
CreateTicketResolutionCodeRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
type DeleteTicketResolutionCodeRequest struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CreateTicketPriorityConfigRequest struct {
|
|
||||||
Name string `json:"name"`
|
|
||||||
FirstResponseMinutes int `json:"firstResponseMinutes"`
|
|
||||||
ResolutionMinutes int `json:"resolutionMinutes"`
|
|
||||||
Status enums.Status `json:"status"`
|
|
||||||
Remark string `json:"remark"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type UpdateTicketPriorityConfigRequest struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
CreateTicketPriorityConfigRequest
|
|
||||||
}
|
|
||||||
|
|
||||||
type DeleteTicketPriorityConfigRequest struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
}
|
|
||||||
@@ -1,143 +1,54 @@
|
|||||||
package request
|
package request
|
||||||
|
|
||||||
type CreateTicketRequest struct {
|
type CreateTicketRequest struct {
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Source string `json:"source"`
|
Source string `json:"source"`
|
||||||
Channel string `json:"channel"`
|
Channel string `json:"channel"`
|
||||||
CustomerID int64 `json:"customerId"`
|
CustomerID int64 `json:"customerId"`
|
||||||
ConversationID int64 `json:"conversationId"`
|
ConversationID int64 `json:"conversationId"`
|
||||||
TagIDs []int64 `json:"tagIds"`
|
TagIDs []int64 `json:"tagIds"`
|
||||||
Type string `json:"type"`
|
CurrentAssigneeID int64 `json:"currentAssigneeId"`
|
||||||
Priority int64 `json:"priority"`
|
|
||||||
Severity int `json:"severity"`
|
|
||||||
CurrentTeamID int64 `json:"currentTeamId"`
|
|
||||||
CurrentAssigneeID int64 `json:"currentAssigneeId"`
|
|
||||||
DueAt string `json:"dueAt"`
|
|
||||||
CustomFields map[string]any `json:"customFields"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateTicketFromConversationRequest struct {
|
type CreateTicketFromConversationRequest struct {
|
||||||
ConversationID int64 `json:"conversationId"`
|
ConversationID int64 `json:"conversationId"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
TagIDs []int64 `json:"tagIds"`
|
TagIDs []int64 `json:"tagIds"`
|
||||||
Priority int64 `json:"priority"`
|
CurrentAssigneeID int64 `json:"currentAssigneeId"`
|
||||||
Severity int `json:"severity"`
|
|
||||||
CurrentTeamID int64 `json:"currentTeamId"`
|
|
||||||
CurrentAssigneeID int64 `json:"currentAssigneeId"`
|
|
||||||
SyncToConversation bool `json:"syncToConversation"`
|
|
||||||
CustomFields map[string]any `json:"customFields"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateTicketRequest struct {
|
type UpdateTicketRequest struct {
|
||||||
TicketID int64 `json:"ticketId"`
|
TicketID int64 `json:"ticketId"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
TagIDs []int64 `json:"tagIds"`
|
TagIDs []int64 `json:"tagIds"`
|
||||||
Type string `json:"type"`
|
CurrentAssigneeID int64 `json:"currentAssigneeId"`
|
||||||
Priority int64 `json:"priority"`
|
|
||||||
Severity int `json:"severity"`
|
|
||||||
CurrentTeamID int64 `json:"currentTeamId"`
|
|
||||||
CurrentAssigneeID int64 `json:"currentAssigneeId"`
|
|
||||||
DueAt string `json:"dueAt"`
|
|
||||||
CustomFields map[string]any `json:"customFields"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AssignTicketRequest struct {
|
type AssignTicketRequest struct {
|
||||||
TicketID int64 `json:"ticketId"`
|
TicketID int64 `json:"ticketId"`
|
||||||
ToUserID int64 `json:"toUserId"`
|
ToUserID int64 `json:"toUserId"`
|
||||||
ToTeamID int64 `json:"toTeamId"`
|
|
||||||
Reason string `json:"reason"`
|
Reason string `json:"reason"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChangeTicketStatusRequest struct {
|
type ChangeTicketStatusRequest struct {
|
||||||
TicketID int64 `json:"ticketId"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
PendingReason string `json:"pendingReason"`
|
|
||||||
CloseReason string `json:"closeReason"`
|
|
||||||
ResolutionCode string `json:"resolutionCode"`
|
|
||||||
ResolutionSummary string `json:"resolutionSummary"`
|
|
||||||
Reason string `json:"reason"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReplyTicketRequest struct {
|
|
||||||
TicketID int64 `json:"ticketId"`
|
|
||||||
ContentType string `json:"contentType"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
Payload string `json:"payload"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type InternalNoteRequest struct {
|
|
||||||
TicketID int64 `json:"ticketId"`
|
|
||||||
ContentType string `json:"contentType"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
Payload string `json:"payload"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type CloseTicketRequest struct {
|
|
||||||
TicketID int64 `json:"ticketId"`
|
|
||||||
CloseReason string `json:"closeReason"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ReopenTicketRequest struct {
|
|
||||||
TicketID int64 `json:"ticketId"`
|
TicketID int64 `json:"ticketId"`
|
||||||
Reason string `json:"reason"`
|
Status string `json:"status"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type WatchTicketRequest struct {
|
type CreateTicketProgressRequest struct {
|
||||||
TicketID int64 `json:"ticketId"`
|
TicketID int64 `json:"ticketId"`
|
||||||
|
Content string `json:"content"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type BatchAssignTicketRequest struct {
|
type BatchAssignTicketRequest struct {
|
||||||
TicketIDs []int64 `json:"ticketIds"`
|
TicketIDs []int64 `json:"ticketIds"`
|
||||||
ToUserID int64 `json:"toUserId"`
|
ToUserID int64 `json:"toUserId"`
|
||||||
ToTeamID int64 `json:"toTeamId"`
|
|
||||||
Reason string `json:"reason"`
|
Reason string `json:"reason"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type BatchChangeTicketStatusRequest struct {
|
|
||||||
TicketIDs []int64 `json:"ticketIds"`
|
|
||||||
Status string `json:"status"`
|
|
||||||
PendingReason string `json:"pendingReason"`
|
|
||||||
CloseReason string `json:"closeReason"`
|
|
||||||
ResolutionCode string `json:"resolutionCode"`
|
|
||||||
ResolutionSummary string `json:"resolutionSummary"`
|
|
||||||
Reason string `json:"reason"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type BatchWatchTicketRequest struct {
|
|
||||||
TicketIDs []int64 `json:"ticketIds"`
|
|
||||||
Watched bool `json:"watched"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AddTicketRelationRequest struct {
|
|
||||||
TicketID int64 `json:"ticketId"`
|
|
||||||
RelatedTicketID int64 `json:"relatedTicketId"`
|
|
||||||
RelatedTicketNo string `json:"relatedTicketNo"`
|
|
||||||
RelationType string `json:"relationType"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DeleteTicketRelationRequest struct {
|
|
||||||
TicketID int64 `json:"ticketId"`
|
|
||||||
RelationID int64 `json:"relationId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type AddTicketCollaboratorRequest struct {
|
|
||||||
TicketID int64 `json:"ticketId"`
|
|
||||||
UserID int64 `json:"userId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DeleteTicketCollaboratorRequest struct {
|
|
||||||
TicketID int64 `json:"ticketId"`
|
|
||||||
CollaboratorID int64 `json:"collaboratorId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type LinkTicketCustomerRequest struct {
|
|
||||||
TicketID int64 `json:"ticketId"`
|
|
||||||
CustomerID int64 `json:"customerId"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type SaveTicketViewRequest struct {
|
type SaveTicketViewRequest struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
package response
|
|
||||||
|
|
||||||
import "cs-agent/internal/pkg/enums"
|
|
||||||
|
|
||||||
type TicketResolutionCodeResponse struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Code string `json:"code"`
|
|
||||||
SortNo int `json:"sortNo"`
|
|
||||||
Status enums.Status `json:"status"`
|
|
||||||
Remark string `json:"remark"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TicketPriorityConfigResponse struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
SortNo int `json:"sortNo"`
|
|
||||||
FirstResponseMinutes int `json:"firstResponseMinutes"`
|
|
||||||
ResolutionMinutes int `json:"resolutionMinutes"`
|
|
||||||
Status enums.Status `json:"status"`
|
|
||||||
Remark string `json:"remark"`
|
|
||||||
}
|
|
||||||
@@ -2,145 +2,49 @@ package response
|
|||||||
|
|
||||||
import "cs-agent/internal/pkg/enums"
|
import "cs-agent/internal/pkg/enums"
|
||||||
|
|
||||||
type TicketSLAResponse struct {
|
type TicketProgressResponse struct {
|
||||||
SLAType enums.TicketSLAType `json:"slaType"`
|
ID int64 `json:"id"`
|
||||||
TargetMinutes int `json:"targetMinutes"`
|
TicketID int64 `json:"ticketId"`
|
||||||
Status enums.TicketSLAStatus `json:"status"`
|
Content string `json:"content"`
|
||||||
StartedAt string `json:"startedAt,omitempty"`
|
AuthorID int64 `json:"authorId"`
|
||||||
PausedAt string `json:"pausedAt,omitempty"`
|
AuthorName string `json:"authorName,omitempty"`
|
||||||
StoppedAt string `json:"stoppedAt,omitempty"`
|
CreatedAt string `json:"createdAt,omitempty"`
|
||||||
BreachedAt string `json:"breachedAt,omitempty"`
|
|
||||||
ElapsedMin int `json:"elapsedMin"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TicketWatcherResponse struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
UserID int64 `json:"userId"`
|
|
||||||
UserName string `json:"userName,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TicketCommentResponse struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
TicketID int64 `json:"ticketId"`
|
|
||||||
CommentType enums.TicketCommentType `json:"commentType"`
|
|
||||||
AuthorType enums.IMSenderType `json:"authorType"`
|
|
||||||
AuthorID int64 `json:"authorId"`
|
|
||||||
AuthorName string `json:"authorName,omitempty"`
|
|
||||||
ContentType string `json:"contentType"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
Payload string `json:"payload,omitempty"`
|
|
||||||
CreatedAt string `json:"createdAt,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TicketEventLogResponse struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
TicketID int64 `json:"ticketId"`
|
|
||||||
EventType enums.TicketEventType `json:"eventType"`
|
|
||||||
OperatorType enums.IMSenderType `json:"operatorType"`
|
|
||||||
OperatorID int64 `json:"operatorId"`
|
|
||||||
OperatorName string `json:"operatorName,omitempty"`
|
|
||||||
OldValue string `json:"oldValue,omitempty"`
|
|
||||||
NewValue string `json:"newValue,omitempty"`
|
|
||||||
Content string `json:"content,omitempty"`
|
|
||||||
Payload string `json:"payload,omitempty"`
|
|
||||||
CreatedAt string `json:"createdAt,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type TicketResponse struct {
|
type TicketResponse struct {
|
||||||
ID int64 `json:"id"`
|
ID int64 `json:"id"`
|
||||||
TicketNo string `json:"ticketNo"`
|
TicketNo string `json:"ticketNo"`
|
||||||
Title string `json:"title"`
|
Title string `json:"title"`
|
||||||
Description string `json:"description"`
|
Description string `json:"description"`
|
||||||
Source enums.TicketSource `json:"source"`
|
Source enums.TicketSource `json:"source"`
|
||||||
Channel string `json:"channel"`
|
Channel string `json:"channel"`
|
||||||
CustomerID int64 `json:"customerId"`
|
CustomerID int64 `json:"customerId"`
|
||||||
ConversationID int64 `json:"conversationId"`
|
ConversationID int64 `json:"conversationId"`
|
||||||
Tags []TagResponse `json:"tags,omitempty"`
|
Tags []TagResponse `json:"tags,omitempty"`
|
||||||
Type string `json:"type"`
|
Status enums.TicketStatus `json:"status"`
|
||||||
Priority int64 `json:"priority"`
|
CurrentAssigneeID int64 `json:"currentAssigneeId"`
|
||||||
PriorityName string `json:"priorityName,omitempty"`
|
CurrentAssigneeName string `json:"currentAssigneeName,omitempty"`
|
||||||
Severity enums.TicketSeverity `json:"severity"`
|
CreatedBy int64 `json:"createdBy"`
|
||||||
Status enums.TicketStatus `json:"status"`
|
CreatedByName string `json:"createdByName,omitempty"`
|
||||||
CurrentTeamID int64 `json:"currentTeamId"`
|
HandledAt string `json:"handledAt,omitempty"`
|
||||||
CurrentTeamName string `json:"currentTeamName,omitempty"`
|
CreatedAt string `json:"createdAt,omitempty"`
|
||||||
CurrentAssigneeID int64 `json:"currentAssigneeId"`
|
UpdatedAt string `json:"updatedAt,omitempty"`
|
||||||
CurrentAssigneeName string `json:"currentAssigneeName,omitempty"`
|
Customer *CustomerResponse `json:"customer,omitempty"`
|
||||||
WatchedByMe bool `json:"watchedByMe"`
|
|
||||||
PendingReason string `json:"pendingReason,omitempty"`
|
|
||||||
CloseReason string `json:"closeReason,omitempty"`
|
|
||||||
ResolutionCode string `json:"resolutionCode,omitempty"`
|
|
||||||
ResolutionCodeName string `json:"resolutionCodeName,omitempty"`
|
|
||||||
ResolutionSummary string `json:"resolutionSummary,omitempty"`
|
|
||||||
FirstResponseAt string `json:"firstResponseAt,omitempty"`
|
|
||||||
ResolvedAt string `json:"resolvedAt,omitempty"`
|
|
||||||
ClosedAt string `json:"closedAt,omitempty"`
|
|
||||||
DueAt string `json:"dueAt,omitempty"`
|
|
||||||
NextReplyDeadlineAt string `json:"nextReplyDeadlineAt,omitempty"`
|
|
||||||
ResolveDeadlineAt string `json:"resolveDeadlineAt,omitempty"`
|
|
||||||
ReopenedCount int `json:"reopenedCount"`
|
|
||||||
CreatedAt string `json:"createdAt,omitempty"`
|
|
||||||
UpdatedAt string `json:"updatedAt,omitempty"`
|
|
||||||
Customer *CustomerResponse `json:"customer,omitempty"`
|
|
||||||
SLA []TicketSLAResponse `json:"sla,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type TicketDetailResponse struct {
|
type TicketDetailResponse struct {
|
||||||
Ticket TicketResponse `json:"ticket"`
|
Ticket TicketResponse `json:"ticket"`
|
||||||
Watchers []TicketWatcherResponse `json:"watchers,omitempty"`
|
Progresses []TicketProgressResponse `json:"progresses,omitempty"`
|
||||||
Collaborators []TicketCollaboratorResponse `json:"collaborators,omitempty"`
|
|
||||||
Comments []TicketCommentResponse `json:"comments,omitempty"`
|
|
||||||
Events []TicketEventLogResponse `json:"events,omitempty"`
|
|
||||||
RelatedTickets []TicketRelationResponse `json:"relatedTickets,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type TicketSummaryResponse struct {
|
type TicketSummaryResponse struct {
|
||||||
All int64 `json:"all"`
|
All int64 `json:"all"`
|
||||||
Mine int64 `json:"mine"`
|
Pending int64 `json:"pending"`
|
||||||
Watching int64 `json:"watching"`
|
InProgress int64 `json:"inProgress"`
|
||||||
Collaboration int64 `json:"collaboration"`
|
Done int64 `json:"done"`
|
||||||
Participating int64 `json:"participating"`
|
Unassigned int64 `json:"unassigned"`
|
||||||
Mentioned int64 `json:"mentioned"`
|
Mine int64 `json:"mine"`
|
||||||
Unassigned int64 `json:"unassigned"`
|
Stale int64 `json:"stale"`
|
||||||
PendingCustomer int64 `json:"pendingCustomer"`
|
|
||||||
PendingInternal int64 `json:"pendingInternal"`
|
|
||||||
Overdue int64 `json:"overdue"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TicketRiskReasonResponse struct {
|
|
||||||
Code string `json:"code"`
|
|
||||||
Title string `json:"title"`
|
|
||||||
Description string `json:"description"`
|
|
||||||
Count int64 `json:"count"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TicketRiskOverviewResponse struct {
|
|
||||||
Overdue int64 `json:"overdue"`
|
|
||||||
HighRisk int64 `json:"highRisk"`
|
|
||||||
Unassigned int64 `json:"unassigned"`
|
|
||||||
PendingInternal int64 `json:"pendingInternal"`
|
|
||||||
PendingCustomer int64 `json:"pendingCustomer"`
|
|
||||||
RiskWindowMins int `json:"riskWindowMins"`
|
|
||||||
Reasons []TicketRiskReasonResponse `json:"reasons,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TicketRelationResponse struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
TicketID int64 `json:"ticketId"`
|
|
||||||
RelatedTicketID int64 `json:"relatedTicketId"`
|
|
||||||
RelationType enums.TicketRelationType `json:"relationType"`
|
|
||||||
RelatedTicketNo string `json:"relatedTicketNo,omitempty"`
|
|
||||||
RelatedTicketTitle string `json:"relatedTicketTitle,omitempty"`
|
|
||||||
RelatedTicketStatus enums.TicketStatus `json:"relatedTicketStatus,omitempty"`
|
|
||||||
CurrentTeamName string `json:"currentTeamName,omitempty"`
|
|
||||||
CurrentAssigneeName string `json:"currentAssigneeName,omitempty"`
|
|
||||||
UpdatedAt string `json:"updatedAt,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TicketCollaboratorResponse struct {
|
|
||||||
ID int64 `json:"id"`
|
|
||||||
UserID int64 `json:"userId"`
|
|
||||||
UserName string `json:"userName,omitempty"`
|
|
||||||
TeamName string `json:"teamName,omitempty"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type TicketViewResponse struct {
|
type TicketViewResponse struct {
|
||||||
|
|||||||
@@ -3,33 +3,21 @@ package enums
|
|||||||
type TicketStatus string
|
type TicketStatus string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TicketStatusNew TicketStatus = "new"
|
TicketStatusPending TicketStatus = "pending"
|
||||||
TicketStatusOpen TicketStatus = "open"
|
TicketStatusInProgress TicketStatus = "in_progress"
|
||||||
TicketStatusPendingCustomer TicketStatus = "pending_customer"
|
TicketStatusDone TicketStatus = "done"
|
||||||
TicketStatusPendingInternal TicketStatus = "pending_internal"
|
|
||||||
TicketStatusResolved TicketStatus = "resolved"
|
|
||||||
TicketStatusClosed TicketStatus = "closed"
|
|
||||||
TicketStatusCancelled TicketStatus = "cancelled"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var TicketStatusValues = []TicketStatus{
|
var TicketStatusValues = []TicketStatus{
|
||||||
TicketStatusNew,
|
TicketStatusPending,
|
||||||
TicketStatusOpen,
|
TicketStatusInProgress,
|
||||||
TicketStatusPendingCustomer,
|
TicketStatusDone,
|
||||||
TicketStatusPendingInternal,
|
|
||||||
TicketStatusResolved,
|
|
||||||
TicketStatusClosed,
|
|
||||||
TicketStatusCancelled,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var ticketStatusLabelMap = map[TicketStatus]string{
|
var ticketStatusLabelMap = map[TicketStatus]string{
|
||||||
TicketStatusNew: "新建",
|
TicketStatusPending: "待处理",
|
||||||
TicketStatusOpen: "处理中",
|
TicketStatusInProgress: "处理中",
|
||||||
TicketStatusPendingCustomer: "待客户反馈",
|
TicketStatusDone: "已处理",
|
||||||
TicketStatusPendingInternal: "待内部处理",
|
|
||||||
TicketStatusResolved: "已解决",
|
|
||||||
TicketStatusClosed: "已关闭",
|
|
||||||
TicketStatusCancelled: "已取消",
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetTicketStatusLabel(status TicketStatus) string {
|
func GetTicketStatusLabel(status TicketStatus) string {
|
||||||
@@ -45,55 +33,16 @@ func IsValidTicketStatus(status string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
type TicketSeverity int
|
|
||||||
|
|
||||||
const (
|
|
||||||
TicketSeverityMinor TicketSeverity = 1
|
|
||||||
TicketSeverityMajor TicketSeverity = 2
|
|
||||||
TicketSeverityCritical TicketSeverity = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
var TicketSeverityValues = []TicketSeverity{
|
|
||||||
TicketSeverityMinor,
|
|
||||||
TicketSeverityMajor,
|
|
||||||
TicketSeverityCritical,
|
|
||||||
}
|
|
||||||
|
|
||||||
var ticketSeverityLabelMap = map[TicketSeverity]string{
|
|
||||||
TicketSeverityMinor: "轻微",
|
|
||||||
TicketSeverityMajor: "严重",
|
|
||||||
TicketSeverityCritical: "致命",
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetTicketSeverityLabel(severity TicketSeverity) string {
|
|
||||||
return ticketSeverityLabelMap[severity]
|
|
||||||
}
|
|
||||||
|
|
||||||
func IsValidTicketSeverity(severity int) bool {
|
|
||||||
for _, item := range TicketSeverityValues {
|
|
||||||
if int(item) == severity {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
type TicketSource string
|
type TicketSource string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
TicketSourceManual TicketSource = "manual"
|
TicketSourceManual TicketSource = "manual"
|
||||||
TicketSourceConversation TicketSource = "conversation"
|
TicketSourceConversation TicketSource = "conversation"
|
||||||
TicketSourcePortal TicketSource = "portal"
|
|
||||||
TicketSourceAPI TicketSource = "api"
|
|
||||||
TicketSourceRule TicketSource = "rule"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var TicketSourceValues = []TicketSource{
|
var TicketSourceValues = []TicketSource{
|
||||||
TicketSourceManual,
|
TicketSourceManual,
|
||||||
TicketSourceConversation,
|
TicketSourceConversation,
|
||||||
TicketSourcePortal,
|
|
||||||
TicketSourceAPI,
|
|
||||||
TicketSourceRule,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func IsValidTicketSource(source string) bool {
|
func IsValidTicketSource(source string) bool {
|
||||||
@@ -104,53 +53,3 @@ func IsValidTicketSource(source string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
type TicketCommentType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
TicketCommentTypePublicReply TicketCommentType = "public_reply"
|
|
||||||
TicketCommentTypeInternalNote TicketCommentType = "internal_note"
|
|
||||||
TicketCommentTypeSystemLog TicketCommentType = "system_log"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TicketEventType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
TicketEventTypeCreated TicketEventType = "created"
|
|
||||||
TicketEventTypeUpdated TicketEventType = "updated"
|
|
||||||
TicketEventTypeAssigned TicketEventType = "assigned"
|
|
||||||
TicketEventTypeTransferred TicketEventType = "transferred"
|
|
||||||
TicketEventTypeStatusChanged TicketEventType = "status_changed"
|
|
||||||
TicketEventTypeReplied TicketEventType = "replied"
|
|
||||||
TicketEventTypeInternalNoted TicketEventType = "internal_noted"
|
|
||||||
TicketEventTypeClosed TicketEventType = "closed"
|
|
||||||
TicketEventTypeReopened TicketEventType = "reopened"
|
|
||||||
TicketEventTypeSLABreached TicketEventType = "sla_breached"
|
|
||||||
TicketEventTypeLinkedConversation TicketEventType = "linked_conversation"
|
|
||||||
TicketEventTypeMentioned TicketEventType = "mentioned"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TicketSLAType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
TicketSLATypeFirstResponse TicketSLAType = "first_response"
|
|
||||||
TicketSLATypeResolution TicketSLAType = "resolution"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TicketSLAStatus string
|
|
||||||
|
|
||||||
const (
|
|
||||||
TicketSLAStatusRunning TicketSLAStatus = "running"
|
|
||||||
TicketSLAStatusPaused TicketSLAStatus = "paused"
|
|
||||||
TicketSLAStatusCompleted TicketSLAStatus = "completed"
|
|
||||||
TicketSLAStatusBreached TicketSLAStatus = "breached"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TicketRelationType string
|
|
||||||
|
|
||||||
const (
|
|
||||||
TicketRelationTypeDuplicate TicketRelationType = "duplicate"
|
|
||||||
TicketRelationTypeRelated TicketRelationType = "related"
|
|
||||||
TicketRelationTypeParent TicketRelationType = "parent"
|
|
||||||
TicketRelationTypeChild TicketRelationType = "child"
|
|
||||||
)
|
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
package repositories
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cs-agent/internal/models"
|
|
||||||
|
|
||||||
"github.com/mlogclub/simple/sqls"
|
|
||||||
"github.com/mlogclub/simple/web/params"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
var TicketCollaboratorRepository = newTicketCollaboratorRepository()
|
|
||||||
|
|
||||||
func newTicketCollaboratorRepository() *ticketCollaboratorRepository {
|
|
||||||
return &ticketCollaboratorRepository{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ticketCollaboratorRepository struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketCollaboratorRepository) TakeByTicketIDAndUserID(db *gorm.DB, ticketID, userID int64) *models.TicketCollaborator {
|
|
||||||
ret := &models.TicketCollaborator{}
|
|
||||||
if err := db.Take(ret, "ticket_id = ? AND user_id = ?", ticketID, userID).Error; err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketCollaboratorRepository) Get(db *gorm.DB, id int64) *models.TicketCollaborator {
|
|
||||||
ret := &models.TicketCollaborator{}
|
|
||||||
if err := db.First(ret, "id = ?", id).Error; err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketCollaboratorRepository) Take(db *gorm.DB, where ...interface{}) *models.TicketCollaborator {
|
|
||||||
ret := &models.TicketCollaborator{}
|
|
||||||
if err := db.Take(ret, where...).Error; err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketCollaboratorRepository) Find(db *gorm.DB, cnd *sqls.Cnd) (list []models.TicketCollaborator) {
|
|
||||||
cnd.Find(db, &list)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketCollaboratorRepository) FindOne(db *gorm.DB, cnd *sqls.Cnd) *models.TicketCollaborator {
|
|
||||||
ret := &models.TicketCollaborator{}
|
|
||||||
if err := cnd.FindOne(db, &ret); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketCollaboratorRepository) FindPageByParams(db *gorm.DB, params *params.QueryParams) (list []models.TicketCollaborator, paging *sqls.Paging) {
|
|
||||||
return r.FindPageByCnd(db, ¶ms.Cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketCollaboratorRepository) FindPageByCnd(db *gorm.DB, cnd *sqls.Cnd) (list []models.TicketCollaborator, paging *sqls.Paging) {
|
|
||||||
cnd.Find(db, &list)
|
|
||||||
count := cnd.Count(db, &models.TicketCollaborator{})
|
|
||||||
|
|
||||||
paging = &sqls.Paging{
|
|
||||||
Page: cnd.Paging.Page,
|
|
||||||
Limit: cnd.Paging.Limit,
|
|
||||||
Total: count,
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketCollaboratorRepository) Count(db *gorm.DB, cnd *sqls.Cnd) int64 {
|
|
||||||
return cnd.Count(db, &models.TicketCollaborator{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketCollaboratorRepository) Create(db *gorm.DB, t *models.TicketCollaborator) error {
|
|
||||||
return db.Create(t).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketCollaboratorRepository) Delete(db *gorm.DB, id int64) {
|
|
||||||
db.Delete(&models.TicketCollaborator{}, "id = ?", id)
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
package repositories
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cs-agent/internal/models"
|
|
||||||
|
|
||||||
"github.com/mlogclub/simple/sqls"
|
|
||||||
"github.com/mlogclub/simple/web/params"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
var TicketCommentRepository = newTicketCommentRepository()
|
|
||||||
|
|
||||||
func newTicketCommentRepository() *ticketCommentRepository {
|
|
||||||
return &ticketCommentRepository{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ticketCommentRepository struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketCommentRepository) Get(db *gorm.DB, id int64) *models.TicketComment {
|
|
||||||
ret := &models.TicketComment{}
|
|
||||||
if err := db.First(ret, "id = ?", id).Error; err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketCommentRepository) Take(db *gorm.DB, where ...interface{}) *models.TicketComment {
|
|
||||||
ret := &models.TicketComment{}
|
|
||||||
if err := db.Take(ret, where...).Error; err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketCommentRepository) Find(db *gorm.DB, cnd *sqls.Cnd) (list []models.TicketComment) {
|
|
||||||
cnd.Find(db, &list)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketCommentRepository) FindOne(db *gorm.DB, cnd *sqls.Cnd) *models.TicketComment {
|
|
||||||
ret := &models.TicketComment{}
|
|
||||||
if err := cnd.FindOne(db, &ret); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketCommentRepository) FindPageByParams(db *gorm.DB, params *params.QueryParams) (list []models.TicketComment, paging *sqls.Paging) {
|
|
||||||
return r.FindPageByCnd(db, ¶ms.Cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketCommentRepository) FindPageByCnd(db *gorm.DB, cnd *sqls.Cnd) (list []models.TicketComment, paging *sqls.Paging) {
|
|
||||||
cnd.Find(db, &list)
|
|
||||||
count := cnd.Count(db, &models.TicketComment{})
|
|
||||||
|
|
||||||
paging = &sqls.Paging{
|
|
||||||
Page: cnd.Paging.Page,
|
|
||||||
Limit: cnd.Paging.Limit,
|
|
||||||
Total: count,
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketCommentRepository) FindBySql(db *gorm.DB, sqlStr string, paramArr... interface{}) (list []models.TicketComment) {
|
|
||||||
db.Raw(sqlStr, paramArr...).Scan(&list)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketCommentRepository) CountBySql(db *gorm.DB, sqlStr string, paramArr... interface{}) (count int64) {
|
|
||||||
db.Raw(sqlStr, paramArr...).Count(&count)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketCommentRepository) Count(db *gorm.DB, cnd *sqls.Cnd) int64 {
|
|
||||||
return cnd.Count(db, &models.TicketComment{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketCommentRepository) Create(db *gorm.DB, t *models.TicketComment) (err error) {
|
|
||||||
err = db.Create(t).Error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketCommentRepository) Update(db *gorm.DB, t *models.TicketComment) (err error) {
|
|
||||||
err = db.Save(t).Error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketCommentRepository) Updates(db *gorm.DB, id int64, columns map[string]interface{}) (err error) {
|
|
||||||
err = db.Model(&models.TicketComment{}).Where("id = ?", id).Updates(columns).Error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketCommentRepository) UpdateColumn(db *gorm.DB, id int64, name string, value interface{}) (err error) {
|
|
||||||
err = db.Model(&models.TicketComment{}).Where("id = ?", id).UpdateColumn(name, value).Error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketCommentRepository) Delete(db *gorm.DB, id int64) {
|
|
||||||
db.Delete(&models.TicketComment{}, "id = ?", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
package repositories
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cs-agent/internal/models"
|
|
||||||
|
|
||||||
"github.com/mlogclub/simple/sqls"
|
|
||||||
"github.com/mlogclub/simple/web/params"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
var TicketEventLogRepository = newTicketEventLogRepository()
|
|
||||||
|
|
||||||
func newTicketEventLogRepository() *ticketEventLogRepository {
|
|
||||||
return &ticketEventLogRepository{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ticketEventLogRepository struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketEventLogRepository) Get(db *gorm.DB, id int64) *models.TicketEventLog {
|
|
||||||
ret := &models.TicketEventLog{}
|
|
||||||
if err := db.First(ret, "id = ?", id).Error; err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketEventLogRepository) Take(db *gorm.DB, where ...interface{}) *models.TicketEventLog {
|
|
||||||
ret := &models.TicketEventLog{}
|
|
||||||
if err := db.Take(ret, where...).Error; err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketEventLogRepository) Find(db *gorm.DB, cnd *sqls.Cnd) (list []models.TicketEventLog) {
|
|
||||||
cnd.Find(db, &list)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketEventLogRepository) FindOne(db *gorm.DB, cnd *sqls.Cnd) *models.TicketEventLog {
|
|
||||||
ret := &models.TicketEventLog{}
|
|
||||||
if err := cnd.FindOne(db, &ret); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketEventLogRepository) FindPageByParams(db *gorm.DB, params *params.QueryParams) (list []models.TicketEventLog, paging *sqls.Paging) {
|
|
||||||
return r.FindPageByCnd(db, ¶ms.Cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketEventLogRepository) FindPageByCnd(db *gorm.DB, cnd *sqls.Cnd) (list []models.TicketEventLog, paging *sqls.Paging) {
|
|
||||||
cnd.Find(db, &list)
|
|
||||||
count := cnd.Count(db, &models.TicketEventLog{})
|
|
||||||
|
|
||||||
paging = &sqls.Paging{
|
|
||||||
Page: cnd.Paging.Page,
|
|
||||||
Limit: cnd.Paging.Limit,
|
|
||||||
Total: count,
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketEventLogRepository) FindBySql(db *gorm.DB, sqlStr string, paramArr... interface{}) (list []models.TicketEventLog) {
|
|
||||||
db.Raw(sqlStr, paramArr...).Scan(&list)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketEventLogRepository) CountBySql(db *gorm.DB, sqlStr string, paramArr... interface{}) (count int64) {
|
|
||||||
db.Raw(sqlStr, paramArr...).Count(&count)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketEventLogRepository) Count(db *gorm.DB, cnd *sqls.Cnd) int64 {
|
|
||||||
return cnd.Count(db, &models.TicketEventLog{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketEventLogRepository) Create(db *gorm.DB, t *models.TicketEventLog) (err error) {
|
|
||||||
err = db.Create(t).Error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketEventLogRepository) Update(db *gorm.DB, t *models.TicketEventLog) (err error) {
|
|
||||||
err = db.Save(t).Error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketEventLogRepository) Updates(db *gorm.DB, id int64, columns map[string]interface{}) (err error) {
|
|
||||||
err = db.Model(&models.TicketEventLog{}).Where("id = ?", id).Updates(columns).Error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketEventLogRepository) UpdateColumn(db *gorm.DB, id int64, name string, value interface{}) (err error) {
|
|
||||||
err = db.Model(&models.TicketEventLog{}).Where("id = ?", id).UpdateColumn(name, value).Error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketEventLogRepository) Delete(db *gorm.DB, id int64) {
|
|
||||||
db.Delete(&models.TicketEventLog{}, "id = ?", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
package repositories
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cs-agent/internal/models"
|
|
||||||
|
|
||||||
"github.com/mlogclub/simple/sqls"
|
|
||||||
"github.com/mlogclub/simple/web/params"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
var TicketMentionRepository = newTicketMentionRepository()
|
|
||||||
|
|
||||||
func newTicketMentionRepository() *ticketMentionRepository {
|
|
||||||
return &ticketMentionRepository{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ticketMentionRepository struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketMentionRepository) TakeByCommentAndUserID(db *gorm.DB, ticketID, commentID, userID int64) *models.TicketMention {
|
|
||||||
ret := &models.TicketMention{}
|
|
||||||
if err := db.Take(ret, "ticket_id = ? AND comment_id = ? AND mentioned_user_id = ?", ticketID, commentID, userID).Error; err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketMentionRepository) Get(db *gorm.DB, id int64) *models.TicketMention {
|
|
||||||
ret := &models.TicketMention{}
|
|
||||||
if err := db.First(ret, "id = ?", id).Error; err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketMentionRepository) Take(db *gorm.DB, where ...interface{}) *models.TicketMention {
|
|
||||||
ret := &models.TicketMention{}
|
|
||||||
if err := db.Take(ret, where...).Error; err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketMentionRepository) Find(db *gorm.DB, cnd *sqls.Cnd) (list []models.TicketMention) {
|
|
||||||
cnd.Find(db, &list)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketMentionRepository) FindOne(db *gorm.DB, cnd *sqls.Cnd) *models.TicketMention {
|
|
||||||
ret := &models.TicketMention{}
|
|
||||||
if err := cnd.FindOne(db, &ret); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketMentionRepository) FindPageByParams(db *gorm.DB, params *params.QueryParams) (list []models.TicketMention, paging *sqls.Paging) {
|
|
||||||
return r.FindPageByCnd(db, ¶ms.Cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketMentionRepository) FindPageByCnd(db *gorm.DB, cnd *sqls.Cnd) (list []models.TicketMention, paging *sqls.Paging) {
|
|
||||||
cnd.Find(db, &list)
|
|
||||||
count := cnd.Count(db, &models.TicketMention{})
|
|
||||||
paging = &sqls.Paging{Page: cnd.Paging.Page, Limit: cnd.Paging.Limit, Total: count}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketMentionRepository) Count(db *gorm.DB, cnd *sqls.Cnd) int64 {
|
|
||||||
return cnd.Count(db, &models.TicketMention{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketMentionRepository) Create(db *gorm.DB, t *models.TicketMention) error {
|
|
||||||
return db.Create(t).Error
|
|
||||||
}
|
|
||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"cs-agent/internal/models"
|
"cs-agent/internal/models"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/mlogclub/simple/sqls"
|
||||||
|
"github.com/mlogclub/simple/web/params"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -15,6 +17,55 @@ func newTicketNoSequenceRepository() *ticketNoSequenceRepository {
|
|||||||
|
|
||||||
type ticketNoSequenceRepository struct{}
|
type ticketNoSequenceRepository struct{}
|
||||||
|
|
||||||
|
func (r *ticketNoSequenceRepository) Get(db *gorm.DB, id int64) *models.TicketNoSequence {
|
||||||
|
ret := &models.TicketNoSequence{}
|
||||||
|
if err := db.First(ret, "id = ?", id).Error; err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ticketNoSequenceRepository) Take(db *gorm.DB, where ...any) *models.TicketNoSequence {
|
||||||
|
ret := &models.TicketNoSequence{}
|
||||||
|
if err := db.Take(ret, where...).Error; err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ticketNoSequenceRepository) Find(db *gorm.DB, cnd *sqls.Cnd) (list []models.TicketNoSequence) {
|
||||||
|
cnd.Find(db, &list)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ticketNoSequenceRepository) FindOne(db *gorm.DB, cnd *sqls.Cnd) *models.TicketNoSequence {
|
||||||
|
ret := &models.TicketNoSequence{}
|
||||||
|
if err := cnd.FindOne(db, &ret); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ticketNoSequenceRepository) FindPageByParams(db *gorm.DB, params *params.QueryParams) (list []models.TicketNoSequence, paging *sqls.Paging) {
|
||||||
|
return r.FindPageByCnd(db, ¶ms.Cnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ticketNoSequenceRepository) FindPageByCnd(db *gorm.DB, cnd *sqls.Cnd) (list []models.TicketNoSequence, paging *sqls.Paging) {
|
||||||
|
cnd.Find(db, &list)
|
||||||
|
count := cnd.Count(db, &models.TicketNoSequence{})
|
||||||
|
|
||||||
|
paging = &sqls.Paging{
|
||||||
|
Page: cnd.Paging.Page,
|
||||||
|
Limit: cnd.Paging.Limit,
|
||||||
|
Total: count,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ticketNoSequenceRepository) Count(db *gorm.DB, cnd *sqls.Cnd) int64 {
|
||||||
|
return cnd.Count(db, &models.TicketNoSequence{})
|
||||||
|
}
|
||||||
|
|
||||||
func (r *ticketNoSequenceRepository) GetByDateKey(db *gorm.DB, dateKey string) *models.TicketNoSequence {
|
func (r *ticketNoSequenceRepository) GetByDateKey(db *gorm.DB, dateKey string) *models.TicketNoSequence {
|
||||||
ret := &models.TicketNoSequence{}
|
ret := &models.TicketNoSequence{}
|
||||||
if err := db.Take(ret, "date_key = ?", dateKey).Error; err != nil {
|
if err := db.Take(ret, "date_key = ?", dateKey).Error; err != nil {
|
||||||
@@ -27,6 +78,22 @@ func (r *ticketNoSequenceRepository) Create(db *gorm.DB, t *models.TicketNoSeque
|
|||||||
return db.Create(t).Error
|
return db.Create(t).Error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *ticketNoSequenceRepository) Update(db *gorm.DB, t *models.TicketNoSequence) error {
|
||||||
|
return db.Save(t).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ticketNoSequenceRepository) Updates(db *gorm.DB, id int64, columns map[string]any) error {
|
||||||
|
return db.Model(&models.TicketNoSequence{}).Where("id = ?", id).Updates(columns).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ticketNoSequenceRepository) UpdateColumn(db *gorm.DB, id int64, name string, value any) error {
|
||||||
|
return db.Model(&models.TicketNoSequence{}).Where("id = ?", id).UpdateColumn(name, value).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ticketNoSequenceRepository) Delete(db *gorm.DB, id int64) {
|
||||||
|
db.Delete(&models.TicketNoSequence{}, "id = ?", id)
|
||||||
|
}
|
||||||
|
|
||||||
func (r *ticketNoSequenceRepository) UpdateNextSeq(db *gorm.DB, id int64, currentSeq, nextSeq int64, updatedAt time.Time) (bool, error) {
|
func (r *ticketNoSequenceRepository) UpdateNextSeq(db *gorm.DB, id int64, currentSeq, nextSeq int64, updatedAt time.Time) (bool, error) {
|
||||||
result := db.Model(&models.TicketNoSequence{}).
|
result := db.Model(&models.TicketNoSequence{}).
|
||||||
Where("id = ? AND next_seq = ?", id, currentSeq).
|
Where("id = ? AND next_seq = ?", id, currentSeq).
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
package repositories
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cs-agent/internal/models"
|
|
||||||
|
|
||||||
"github.com/mlogclub/simple/sqls"
|
|
||||||
"github.com/mlogclub/simple/web/params"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
var TicketPriorityConfigRepository = newTicketPriorityConfigRepository()
|
|
||||||
|
|
||||||
func newTicketPriorityConfigRepository() *ticketPriorityConfigRepository {
|
|
||||||
return &ticketPriorityConfigRepository{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ticketPriorityConfigRepository struct{}
|
|
||||||
|
|
||||||
func (r *ticketPriorityConfigRepository) Get(db *gorm.DB, id int64) *models.TicketPriorityConfig {
|
|
||||||
ret := &models.TicketPriorityConfig{}
|
|
||||||
if err := db.First(ret, id).Error; err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketPriorityConfigRepository) Take(db *gorm.DB, where ...interface{}) *models.TicketPriorityConfig {
|
|
||||||
ret := &models.TicketPriorityConfig{}
|
|
||||||
if err := db.Take(ret, where...).Error; err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketPriorityConfigRepository) Find(db *gorm.DB, cnd *sqls.Cnd) (list []models.TicketPriorityConfig) {
|
|
||||||
cnd.Find(db, &list)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketPriorityConfigRepository) FindOne(db *gorm.DB, cnd *sqls.Cnd) *models.TicketPriorityConfig {
|
|
||||||
ret := &models.TicketPriorityConfig{}
|
|
||||||
if err := cnd.FindOne(db, ret); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketPriorityConfigRepository) FindPageByParams(db *gorm.DB, queryParams *params.QueryParams) (list []models.TicketPriorityConfig, paging *sqls.Paging) {
|
|
||||||
return r.FindPageByCnd(db, &queryParams.Cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketPriorityConfigRepository) FindPageByCnd(db *gorm.DB, cnd *sqls.Cnd) (list []models.TicketPriorityConfig, paging *sqls.Paging) {
|
|
||||||
cnd.Find(db, &list)
|
|
||||||
count := cnd.Count(db, &models.TicketPriorityConfig{})
|
|
||||||
paging = &sqls.Paging{Page: cnd.Paging.Page, Limit: cnd.Paging.Limit, Total: count}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketPriorityConfigRepository) Count(db *gorm.DB, cnd *sqls.Cnd) int64 {
|
|
||||||
return cnd.Count(db, &models.TicketPriorityConfig{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketPriorityConfigRepository) Create(db *gorm.DB, t *models.TicketPriorityConfig) error {
|
|
||||||
return db.Create(t).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketPriorityConfigRepository) Update(db *gorm.DB, t *models.TicketPriorityConfig) error {
|
|
||||||
return db.Save(t).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketPriorityConfigRepository) Updates(db *gorm.DB, id int64, columns map[string]any) error {
|
|
||||||
return db.Model(&models.TicketPriorityConfig{}).Where("id = ?", id).Updates(columns).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketPriorityConfigRepository) UpdateColumn(db *gorm.DB, id int64, name string, value any) error {
|
|
||||||
return db.Model(&models.TicketPriorityConfig{}).Where("id = ?", id).UpdateColumn(name, value).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketPriorityConfigRepository) Delete(db *gorm.DB, id int64) error {
|
|
||||||
return db.Delete(&models.TicketPriorityConfig{}, "id = ?", id).Error
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package repositories
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cs-agent/internal/models"
|
||||||
|
|
||||||
|
"github.com/mlogclub/simple/sqls"
|
||||||
|
"github.com/mlogclub/simple/web/params"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var TicketProgressRepository = newTicketProgressRepository()
|
||||||
|
|
||||||
|
func newTicketProgressRepository() *ticketProgressRepository {
|
||||||
|
return &ticketProgressRepository{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ticketProgressRepository struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ticketProgressRepository) Get(db *gorm.DB, id int64) *models.TicketProgress {
|
||||||
|
ret := &models.TicketProgress{}
|
||||||
|
if err := db.First(ret, "id = ?", id).Error; err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ticketProgressRepository) Take(db *gorm.DB, where ...any) *models.TicketProgress {
|
||||||
|
ret := &models.TicketProgress{}
|
||||||
|
if err := db.Take(ret, where...).Error; err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ticketProgressRepository) Find(db *gorm.DB, cnd *sqls.Cnd) (list []models.TicketProgress) {
|
||||||
|
cnd.Find(db, &list)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ticketProgressRepository) FindOne(db *gorm.DB, cnd *sqls.Cnd) *models.TicketProgress {
|
||||||
|
ret := &models.TicketProgress{}
|
||||||
|
if err := cnd.FindOne(db, &ret); err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ticketProgressRepository) FindPageByParams(db *gorm.DB, params *params.QueryParams) (list []models.TicketProgress, paging *sqls.Paging) {
|
||||||
|
return r.FindPageByCnd(db, ¶ms.Cnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ticketProgressRepository) FindPageByCnd(db *gorm.DB, cnd *sqls.Cnd) (list []models.TicketProgress, paging *sqls.Paging) {
|
||||||
|
cnd.Find(db, &list)
|
||||||
|
count := cnd.Count(db, &models.TicketProgress{})
|
||||||
|
|
||||||
|
paging = &sqls.Paging{
|
||||||
|
Page: cnd.Paging.Page,
|
||||||
|
Limit: cnd.Paging.Limit,
|
||||||
|
Total: count,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ticketProgressRepository) FindBySql(db *gorm.DB, sqlStr string, paramArr ...any) (list []models.TicketProgress) {
|
||||||
|
db.Raw(sqlStr, paramArr...).Scan(&list)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ticketProgressRepository) CountBySql(db *gorm.DB, sqlStr string, paramArr ...any) (count int64) {
|
||||||
|
db.Raw(sqlStr, paramArr...).Count(&count)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ticketProgressRepository) Count(db *gorm.DB, cnd *sqls.Cnd) int64 {
|
||||||
|
return cnd.Count(db, &models.TicketProgress{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ticketProgressRepository) Create(db *gorm.DB, t *models.TicketProgress) (err error) {
|
||||||
|
err = db.Create(t).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ticketProgressRepository) Update(db *gorm.DB, t *models.TicketProgress) (err error) {
|
||||||
|
err = db.Save(t).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ticketProgressRepository) Updates(db *gorm.DB, id int64, columns map[string]any) (err error) {
|
||||||
|
err = db.Model(&models.TicketProgress{}).Where("id = ?", id).Updates(columns).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ticketProgressRepository) UpdateColumn(db *gorm.DB, id int64, name string, value any) (err error) {
|
||||||
|
err = db.Model(&models.TicketProgress{}).Where("id = ?", id).UpdateColumn(name, value).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *ticketProgressRepository) Delete(db *gorm.DB, id int64) {
|
||||||
|
db.Delete(&models.TicketProgress{}, "id = ?", id)
|
||||||
|
}
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
package repositories
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cs-agent/internal/models"
|
|
||||||
|
|
||||||
"github.com/mlogclub/simple/sqls"
|
|
||||||
"github.com/mlogclub/simple/web/params"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
var TicketRelationRepository = newTicketRelationRepository()
|
|
||||||
|
|
||||||
func newTicketRelationRepository() *ticketRelationRepository {
|
|
||||||
return &ticketRelationRepository{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ticketRelationRepository struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketRelationRepository) Get(db *gorm.DB, id int64) *models.TicketRelation {
|
|
||||||
ret := &models.TicketRelation{}
|
|
||||||
if err := db.First(ret, "id = ?", id).Error; err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketRelationRepository) Take(db *gorm.DB, where ...interface{}) *models.TicketRelation {
|
|
||||||
ret := &models.TicketRelation{}
|
|
||||||
if err := db.Take(ret, where...).Error; err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketRelationRepository) Find(db *gorm.DB, cnd *sqls.Cnd) (list []models.TicketRelation) {
|
|
||||||
cnd.Find(db, &list)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketRelationRepository) FindOne(db *gorm.DB, cnd *sqls.Cnd) *models.TicketRelation {
|
|
||||||
ret := &models.TicketRelation{}
|
|
||||||
if err := cnd.FindOne(db, &ret); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketRelationRepository) FindPageByParams(db *gorm.DB, params *params.QueryParams) (list []models.TicketRelation, paging *sqls.Paging) {
|
|
||||||
return r.FindPageByCnd(db, ¶ms.Cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketRelationRepository) FindPageByCnd(db *gorm.DB, cnd *sqls.Cnd) (list []models.TicketRelation, paging *sqls.Paging) {
|
|
||||||
cnd.Find(db, &list)
|
|
||||||
count := cnd.Count(db, &models.TicketRelation{})
|
|
||||||
|
|
||||||
paging = &sqls.Paging{
|
|
||||||
Page: cnd.Paging.Page,
|
|
||||||
Limit: cnd.Paging.Limit,
|
|
||||||
Total: count,
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketRelationRepository) FindBySql(db *gorm.DB, sqlStr string, paramArr ...interface{}) (list []models.TicketRelation) {
|
|
||||||
db.Raw(sqlStr, paramArr...).Scan(&list)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketRelationRepository) CountBySql(db *gorm.DB, sqlStr string, paramArr ...interface{}) (count int64) {
|
|
||||||
db.Raw(sqlStr, paramArr...).Count(&count)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketRelationRepository) Count(db *gorm.DB, cnd *sqls.Cnd) int64 {
|
|
||||||
return cnd.Count(db, &models.TicketRelation{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketRelationRepository) Create(db *gorm.DB, t *models.TicketRelation) (err error) {
|
|
||||||
err = db.Create(t).Error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketRelationRepository) Update(db *gorm.DB, t *models.TicketRelation) (err error) {
|
|
||||||
err = db.Save(t).Error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketRelationRepository) Updates(db *gorm.DB, id int64, columns map[string]interface{}) (err error) {
|
|
||||||
err = db.Model(&models.TicketRelation{}).Where("id = ?", id).Updates(columns).Error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketRelationRepository) UpdateColumn(db *gorm.DB, id int64, name string, value interface{}) (err error) {
|
|
||||||
err = db.Model(&models.TicketRelation{}).Where("id = ?", id).UpdateColumn(name, value).Error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketRelationRepository) Delete(db *gorm.DB, id int64) {
|
|
||||||
db.Delete(&models.TicketRelation{}, "id = ?", id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketRelationRepository) DeleteByTicketRelation(db *gorm.DB, ticketID, relatedTicketID int64, relationType string) error {
|
|
||||||
return db.Where("ticket_id = ? AND related_ticket_id = ? AND relation_type = ?", ticketID, relatedTicketID, relationType).
|
|
||||||
Delete(&models.TicketRelation{}).Error
|
|
||||||
}
|
|
||||||
@@ -1,81 +0,0 @@
|
|||||||
package repositories
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cs-agent/internal/models"
|
|
||||||
|
|
||||||
"github.com/mlogclub/simple/sqls"
|
|
||||||
"github.com/mlogclub/simple/web/params"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
var TicketResolutionCodeRepository = newTicketResolutionCodeRepository()
|
|
||||||
|
|
||||||
func newTicketResolutionCodeRepository() *ticketResolutionCodeRepository {
|
|
||||||
return &ticketResolutionCodeRepository{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ticketResolutionCodeRepository struct{}
|
|
||||||
|
|
||||||
func (r *ticketResolutionCodeRepository) Get(db *gorm.DB, id int64) *models.TicketResolutionCode {
|
|
||||||
ret := &models.TicketResolutionCode{}
|
|
||||||
if err := db.First(ret, "id = ?", id).Error; err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketResolutionCodeRepository) Take(db *gorm.DB, where ...interface{}) *models.TicketResolutionCode {
|
|
||||||
ret := &models.TicketResolutionCode{}
|
|
||||||
if err := db.Take(ret, where...).Error; err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketResolutionCodeRepository) Find(db *gorm.DB, cnd *sqls.Cnd) (list []models.TicketResolutionCode) {
|
|
||||||
cnd.Find(db, &list)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketResolutionCodeRepository) FindOne(db *gorm.DB, cnd *sqls.Cnd) *models.TicketResolutionCode {
|
|
||||||
ret := &models.TicketResolutionCode{}
|
|
||||||
if err := cnd.FindOne(db, &ret); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketResolutionCodeRepository) FindPageByParams(db *gorm.DB, params *params.QueryParams) (list []models.TicketResolutionCode, paging *sqls.Paging) {
|
|
||||||
return r.FindPageByCnd(db, ¶ms.Cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketResolutionCodeRepository) FindPageByCnd(db *gorm.DB, cnd *sqls.Cnd) (list []models.TicketResolutionCode, paging *sqls.Paging) {
|
|
||||||
cnd.Find(db, &list)
|
|
||||||
count := cnd.Count(db, &models.TicketResolutionCode{})
|
|
||||||
paging = &sqls.Paging{Page: cnd.Paging.Page, Limit: cnd.Paging.Limit, Total: count}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketResolutionCodeRepository) Count(db *gorm.DB, cnd *sqls.Cnd) int64 {
|
|
||||||
return cnd.Count(db, &models.TicketResolutionCode{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketResolutionCodeRepository) Create(db *gorm.DB, t *models.TicketResolutionCode) error {
|
|
||||||
return db.Create(t).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketResolutionCodeRepository) Update(db *gorm.DB, t *models.TicketResolutionCode) error {
|
|
||||||
return db.Save(t).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketResolutionCodeRepository) Updates(db *gorm.DB, id int64, columns map[string]interface{}) error {
|
|
||||||
return db.Model(&models.TicketResolutionCode{}).Where("id = ?", id).Updates(columns).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketResolutionCodeRepository) UpdateColumn(db *gorm.DB, id int64, name string, value interface{}) error {
|
|
||||||
return db.Model(&models.TicketResolutionCode{}).Where("id = ?", id).UpdateColumn(name, value).Error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketResolutionCodeRepository) Delete(db *gorm.DB, id int64) {
|
|
||||||
db.Delete(&models.TicketResolutionCode{}, "id = ?", id)
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
package repositories
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cs-agent/internal/models"
|
|
||||||
|
|
||||||
"github.com/mlogclub/simple/sqls"
|
|
||||||
"github.com/mlogclub/simple/web/params"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
var TicketSLARecordRepository = newTicketSLARecordRepository()
|
|
||||||
|
|
||||||
func newTicketSLARecordRepository() *ticketSLARecordRepository {
|
|
||||||
return &ticketSLARecordRepository{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ticketSLARecordRepository struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketSLARecordRepository) TakeByTicketIDAndType(db *gorm.DB, ticketID int64, slaType string) *models.TicketSLARecord {
|
|
||||||
ret := &models.TicketSLARecord{}
|
|
||||||
if err := db.Take(ret, "ticket_id = ? AND sla_type = ?", ticketID, slaType).Error; err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketSLARecordRepository) Get(db *gorm.DB, id int64) *models.TicketSLARecord {
|
|
||||||
ret := &models.TicketSLARecord{}
|
|
||||||
if err := db.First(ret, "id = ?", id).Error; err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketSLARecordRepository) Take(db *gorm.DB, where ...interface{}) *models.TicketSLARecord {
|
|
||||||
ret := &models.TicketSLARecord{}
|
|
||||||
if err := db.Take(ret, where...).Error; err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketSLARecordRepository) Find(db *gorm.DB, cnd *sqls.Cnd) (list []models.TicketSLARecord) {
|
|
||||||
cnd.Find(db, &list)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketSLARecordRepository) FindOne(db *gorm.DB, cnd *sqls.Cnd) *models.TicketSLARecord {
|
|
||||||
ret := &models.TicketSLARecord{}
|
|
||||||
if err := cnd.FindOne(db, &ret); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketSLARecordRepository) FindPageByParams(db *gorm.DB, params *params.QueryParams) (list []models.TicketSLARecord, paging *sqls.Paging) {
|
|
||||||
return r.FindPageByCnd(db, ¶ms.Cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketSLARecordRepository) FindPageByCnd(db *gorm.DB, cnd *sqls.Cnd) (list []models.TicketSLARecord, paging *sqls.Paging) {
|
|
||||||
cnd.Find(db, &list)
|
|
||||||
count := cnd.Count(db, &models.TicketSLARecord{})
|
|
||||||
|
|
||||||
paging = &sqls.Paging{
|
|
||||||
Page: cnd.Paging.Page,
|
|
||||||
Limit: cnd.Paging.Limit,
|
|
||||||
Total: count,
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketSLARecordRepository) FindBySql(db *gorm.DB, sqlStr string, paramArr ...interface{}) (list []models.TicketSLARecord) {
|
|
||||||
db.Raw(sqlStr, paramArr...).Scan(&list)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketSLARecordRepository) CountBySql(db *gorm.DB, sqlStr string, paramArr ...interface{}) (count int64) {
|
|
||||||
db.Raw(sqlStr, paramArr...).Count(&count)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketSLARecordRepository) Count(db *gorm.DB, cnd *sqls.Cnd) int64 {
|
|
||||||
return cnd.Count(db, &models.TicketSLARecord{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketSLARecordRepository) Create(db *gorm.DB, t *models.TicketSLARecord) (err error) {
|
|
||||||
err = db.Create(t).Error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketSLARecordRepository) Update(db *gorm.DB, t *models.TicketSLARecord) (err error) {
|
|
||||||
err = db.Save(t).Error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketSLARecordRepository) Updates(db *gorm.DB, id int64, columns map[string]interface{}) (err error) {
|
|
||||||
err = db.Model(&models.TicketSLARecord{}).Where("id = ?", id).Updates(columns).Error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketSLARecordRepository) UpdateColumn(db *gorm.DB, id int64, name string, value interface{}) (err error) {
|
|
||||||
err = db.Model(&models.TicketSLARecord{}).Where("id = ?", id).UpdateColumn(name, value).Error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketSLARecordRepository) Delete(db *gorm.DB, id int64) {
|
|
||||||
db.Delete(&models.TicketSLARecord{}, "id = ?", id)
|
|
||||||
}
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
package repositories
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cs-agent/internal/models"
|
|
||||||
|
|
||||||
"github.com/mlogclub/simple/sqls"
|
|
||||||
"github.com/mlogclub/simple/web/params"
|
|
||||||
"gorm.io/gorm"
|
|
||||||
)
|
|
||||||
|
|
||||||
var TicketWatcherRepository = newTicketWatcherRepository()
|
|
||||||
|
|
||||||
func newTicketWatcherRepository() *ticketWatcherRepository {
|
|
||||||
return &ticketWatcherRepository{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ticketWatcherRepository struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketWatcherRepository) TakeByTicketIDAndUserID(db *gorm.DB, ticketID, userID int64) *models.TicketWatcher {
|
|
||||||
ret := &models.TicketWatcher{}
|
|
||||||
if err := db.Take(ret, "ticket_id = ? AND user_id = ?", ticketID, userID).Error; err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketWatcherRepository) Get(db *gorm.DB, id int64) *models.TicketWatcher {
|
|
||||||
ret := &models.TicketWatcher{}
|
|
||||||
if err := db.First(ret, "id = ?", id).Error; err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketWatcherRepository) Take(db *gorm.DB, where ...interface{}) *models.TicketWatcher {
|
|
||||||
ret := &models.TicketWatcher{}
|
|
||||||
if err := db.Take(ret, where...).Error; err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketWatcherRepository) Find(db *gorm.DB, cnd *sqls.Cnd) (list []models.TicketWatcher) {
|
|
||||||
cnd.Find(db, &list)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketWatcherRepository) FindOne(db *gorm.DB, cnd *sqls.Cnd) *models.TicketWatcher {
|
|
||||||
ret := &models.TicketWatcher{}
|
|
||||||
if err := cnd.FindOne(db, &ret); err != nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return ret
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketWatcherRepository) FindPageByParams(db *gorm.DB, params *params.QueryParams) (list []models.TicketWatcher, paging *sqls.Paging) {
|
|
||||||
return r.FindPageByCnd(db, ¶ms.Cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketWatcherRepository) FindPageByCnd(db *gorm.DB, cnd *sqls.Cnd) (list []models.TicketWatcher, paging *sqls.Paging) {
|
|
||||||
cnd.Find(db, &list)
|
|
||||||
count := cnd.Count(db, &models.TicketWatcher{})
|
|
||||||
|
|
||||||
paging = &sqls.Paging{
|
|
||||||
Page: cnd.Paging.Page,
|
|
||||||
Limit: cnd.Paging.Limit,
|
|
||||||
Total: count,
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketWatcherRepository) FindBySql(db *gorm.DB, sqlStr string, paramArr ...interface{}) (list []models.TicketWatcher) {
|
|
||||||
db.Raw(sqlStr, paramArr...).Scan(&list)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketWatcherRepository) CountBySql(db *gorm.DB, sqlStr string, paramArr ...interface{}) (count int64) {
|
|
||||||
db.Raw(sqlStr, paramArr...).Count(&count)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketWatcherRepository) Count(db *gorm.DB, cnd *sqls.Cnd) int64 {
|
|
||||||
return cnd.Count(db, &models.TicketWatcher{})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketWatcherRepository) Create(db *gorm.DB, t *models.TicketWatcher) (err error) {
|
|
||||||
err = db.Create(t).Error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketWatcherRepository) Update(db *gorm.DB, t *models.TicketWatcher) (err error) {
|
|
||||||
err = db.Save(t).Error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketWatcherRepository) Updates(db *gorm.DB, id int64, columns map[string]interface{}) (err error) {
|
|
||||||
err = db.Model(&models.TicketWatcher{}).Where("id = ?", id).Updates(columns).Error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketWatcherRepository) UpdateColumn(db *gorm.DB, id int64, name string, value interface{}) (err error) {
|
|
||||||
err = db.Model(&models.TicketWatcher{}).Where("id = ?", id).UpdateColumn(name, value).Error
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *ticketWatcherRepository) Delete(db *gorm.DB, id int64) {
|
|
||||||
db.Delete(&models.TicketWatcher{}, "id = ?", id)
|
|
||||||
}
|
|
||||||
@@ -28,17 +28,6 @@ func Init() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
addFunc(c, "@every 1m", func() {
|
|
||||||
count, err := services.TicketService.ScanAndMarkBreachedSLAs(200)
|
|
||||||
if err != nil {
|
|
||||||
slog.Warn("scan breached ticket slas failed", "error", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if count > 0 {
|
|
||||||
slog.Info("ticket sla breached scan completed", "breachedCount", count)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
c.Start()
|
c.Start()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ func TestTicketAssignedInAppNotification(t *testing.T) {
|
|||||||
TicketNo: "TK202604280001",
|
TicketNo: "TK202604280001",
|
||||||
Title: "退款处理",
|
Title: "退款处理",
|
||||||
Source: enums.TicketSourceManual,
|
Source: enums.TicketSourceManual,
|
||||||
Status: enums.TicketStatusOpen,
|
Status: enums.TicketStatusPending,
|
||||||
CurrentAssigneeID: 11,
|
CurrentAssigneeID: 11,
|
||||||
AuditFields: models.AuditFields{
|
AuditFields: models.AuditFields{
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
|
|||||||
@@ -1,54 +0,0 @@
|
|||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cs-agent/internal/models"
|
|
||||||
"cs-agent/internal/repositories"
|
|
||||||
|
|
||||||
"github.com/mlogclub/simple/sqls"
|
|
||||||
"github.com/mlogclub/simple/web/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
var TicketCollaboratorService = newTicketCollaboratorService()
|
|
||||||
|
|
||||||
func newTicketCollaboratorService() *ticketCollaboratorService {
|
|
||||||
return &ticketCollaboratorService{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ticketCollaboratorService struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketCollaboratorService) Get(id int64) *models.TicketCollaborator {
|
|
||||||
return repositories.TicketCollaboratorRepository.Get(sqls.DB(), id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketCollaboratorService) Take(where ...interface{}) *models.TicketCollaborator {
|
|
||||||
return repositories.TicketCollaboratorRepository.Take(sqls.DB(), where...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketCollaboratorService) Find(cnd *sqls.Cnd) []models.TicketCollaborator {
|
|
||||||
return repositories.TicketCollaboratorRepository.Find(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketCollaboratorService) FindOne(cnd *sqls.Cnd) *models.TicketCollaborator {
|
|
||||||
return repositories.TicketCollaboratorRepository.FindOne(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketCollaboratorService) FindPageByParams(params *params.QueryParams) (list []models.TicketCollaborator, paging *sqls.Paging) {
|
|
||||||
return repositories.TicketCollaboratorRepository.FindPageByParams(sqls.DB(), params)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketCollaboratorService) FindPageByCnd(cnd *sqls.Cnd) (list []models.TicketCollaborator, paging *sqls.Paging) {
|
|
||||||
return repositories.TicketCollaboratorRepository.FindPageByCnd(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketCollaboratorService) Count(cnd *sqls.Cnd) int64 {
|
|
||||||
return repositories.TicketCollaboratorRepository.Count(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketCollaboratorService) Create(t *models.TicketCollaborator) error {
|
|
||||||
return repositories.TicketCollaboratorRepository.Create(sqls.DB(), t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketCollaboratorService) Delete(id int64) {
|
|
||||||
repositories.TicketCollaboratorRepository.Delete(sqls.DB(), id)
|
|
||||||
}
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cs-agent/internal/models"
|
|
||||||
"cs-agent/internal/repositories"
|
|
||||||
|
|
||||||
"github.com/mlogclub/simple/sqls"
|
|
||||||
"github.com/mlogclub/simple/web/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
var TicketCommentService = newTicketCommentService()
|
|
||||||
|
|
||||||
func newTicketCommentService() *ticketCommentService {
|
|
||||||
return &ticketCommentService{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ticketCommentService struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketCommentService) Get(id int64) *models.TicketComment {
|
|
||||||
return repositories.TicketCommentRepository.Get(sqls.DB(), id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketCommentService) Take(where ...interface{}) *models.TicketComment {
|
|
||||||
return repositories.TicketCommentRepository.Take(sqls.DB(), where...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketCommentService) Find(cnd *sqls.Cnd) []models.TicketComment {
|
|
||||||
return repositories.TicketCommentRepository.Find(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketCommentService) FindOne(cnd *sqls.Cnd) *models.TicketComment {
|
|
||||||
return repositories.TicketCommentRepository.FindOne(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketCommentService) FindPageByParams(params *params.QueryParams) (list []models.TicketComment, paging *sqls.Paging) {
|
|
||||||
return repositories.TicketCommentRepository.FindPageByParams(sqls.DB(), params)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketCommentService) FindPageByCnd(cnd *sqls.Cnd) (list []models.TicketComment, paging *sqls.Paging) {
|
|
||||||
return repositories.TicketCommentRepository.FindPageByCnd(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketCommentService) Count(cnd *sqls.Cnd) int64 {
|
|
||||||
return repositories.TicketCommentRepository.Count(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketCommentService) Create(t *models.TicketComment) error {
|
|
||||||
return repositories.TicketCommentRepository.Create(sqls.DB(), t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketCommentService) Update(t *models.TicketComment) error {
|
|
||||||
return repositories.TicketCommentRepository.Update(sqls.DB(), t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketCommentService) Updates(id int64, columns map[string]interface{}) error {
|
|
||||||
return repositories.TicketCommentRepository.Updates(sqls.DB(), id, columns)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketCommentService) UpdateColumn(id int64, name string, value interface{}) error {
|
|
||||||
return repositories.TicketCommentRepository.UpdateColumn(sqls.DB(), id, name, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketCommentService) Delete(id int64) {
|
|
||||||
repositories.TicketCommentRepository.Delete(sqls.DB(), id)
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cs-agent/internal/models"
|
|
||||||
"cs-agent/internal/repositories"
|
|
||||||
|
|
||||||
"github.com/mlogclub/simple/sqls"
|
|
||||||
"github.com/mlogclub/simple/web/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
var TicketEventLogService = newTicketEventLogService()
|
|
||||||
|
|
||||||
func newTicketEventLogService() *ticketEventLogService {
|
|
||||||
return &ticketEventLogService{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ticketEventLogService struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketEventLogService) Get(id int64) *models.TicketEventLog {
|
|
||||||
return repositories.TicketEventLogRepository.Get(sqls.DB(), id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketEventLogService) Take(where ...interface{}) *models.TicketEventLog {
|
|
||||||
return repositories.TicketEventLogRepository.Take(sqls.DB(), where...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketEventLogService) Find(cnd *sqls.Cnd) []models.TicketEventLog {
|
|
||||||
return repositories.TicketEventLogRepository.Find(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketEventLogService) FindOne(cnd *sqls.Cnd) *models.TicketEventLog {
|
|
||||||
return repositories.TicketEventLogRepository.FindOne(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketEventLogService) FindPageByParams(params *params.QueryParams) (list []models.TicketEventLog, paging *sqls.Paging) {
|
|
||||||
return repositories.TicketEventLogRepository.FindPageByParams(sqls.DB(), params)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketEventLogService) FindPageByCnd(cnd *sqls.Cnd) (list []models.TicketEventLog, paging *sqls.Paging) {
|
|
||||||
return repositories.TicketEventLogRepository.FindPageByCnd(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketEventLogService) Count(cnd *sqls.Cnd) int64 {
|
|
||||||
return repositories.TicketEventLogRepository.Count(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketEventLogService) Create(t *models.TicketEventLog) error {
|
|
||||||
return repositories.TicketEventLogRepository.Create(sqls.DB(), t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketEventLogService) Update(t *models.TicketEventLog) error {
|
|
||||||
return repositories.TicketEventLogRepository.Update(sqls.DB(), t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketEventLogService) Updates(id int64, columns map[string]interface{}) error {
|
|
||||||
return repositories.TicketEventLogRepository.Updates(sqls.DB(), id, columns)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketEventLogService) UpdateColumn(id int64, name string, value interface{}) error {
|
|
||||||
return repositories.TicketEventLogRepository.UpdateColumn(sqls.DB(), id, name, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketEventLogService) Delete(id int64) {
|
|
||||||
repositories.TicketEventLogRepository.Delete(sqls.DB(), id)
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cs-agent/internal/models"
|
|
||||||
"cs-agent/internal/repositories"
|
|
||||||
|
|
||||||
"github.com/mlogclub/simple/sqls"
|
|
||||||
"github.com/mlogclub/simple/web/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
var TicketMentionService = newTicketMentionService()
|
|
||||||
|
|
||||||
func newTicketMentionService() *ticketMentionService {
|
|
||||||
return &ticketMentionService{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ticketMentionService struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketMentionService) Get(id int64) *models.TicketMention {
|
|
||||||
return repositories.TicketMentionRepository.Get(sqls.DB(), id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketMentionService) Take(where ...interface{}) *models.TicketMention {
|
|
||||||
return repositories.TicketMentionRepository.Take(sqls.DB(), where...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketMentionService) Find(cnd *sqls.Cnd) []models.TicketMention {
|
|
||||||
return repositories.TicketMentionRepository.Find(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketMentionService) FindOne(cnd *sqls.Cnd) *models.TicketMention {
|
|
||||||
return repositories.TicketMentionRepository.FindOne(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketMentionService) FindPageByParams(params *params.QueryParams) (list []models.TicketMention, paging *sqls.Paging) {
|
|
||||||
return repositories.TicketMentionRepository.FindPageByParams(sqls.DB(), params)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketMentionService) FindPageByCnd(cnd *sqls.Cnd) (list []models.TicketMention, paging *sqls.Paging) {
|
|
||||||
return repositories.TicketMentionRepository.FindPageByCnd(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketMentionService) Count(cnd *sqls.Cnd) int64 {
|
|
||||||
return repositories.TicketMentionRepository.Count(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"cs-agent/internal/models"
|
||||||
|
"cs-agent/internal/repositories"
|
||||||
|
|
||||||
|
"github.com/mlogclub/simple/sqls"
|
||||||
|
"github.com/mlogclub/simple/web/params"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
var TicketNoSequenceService = newTicketNoSequenceService()
|
||||||
|
|
||||||
|
func newTicketNoSequenceService() *ticketNoSequenceService {
|
||||||
|
return &ticketNoSequenceService{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ticketNoSequenceService struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ticketNoSequenceService) Get(id int64) *models.TicketNoSequence {
|
||||||
|
return repositories.TicketNoSequenceRepository.Get(sqls.DB(), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ticketNoSequenceService) Take(where ...any) *models.TicketNoSequence {
|
||||||
|
return repositories.TicketNoSequenceRepository.Take(sqls.DB(), where...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ticketNoSequenceService) Find(cnd *sqls.Cnd) []models.TicketNoSequence {
|
||||||
|
return repositories.TicketNoSequenceRepository.Find(sqls.DB(), cnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ticketNoSequenceService) FindOne(cnd *sqls.Cnd) *models.TicketNoSequence {
|
||||||
|
return repositories.TicketNoSequenceRepository.FindOne(sqls.DB(), cnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ticketNoSequenceService) FindPageByParams(params *params.QueryParams) (list []models.TicketNoSequence, paging *sqls.Paging) {
|
||||||
|
return repositories.TicketNoSequenceRepository.FindPageByParams(sqls.DB(), params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ticketNoSequenceService) FindPageByCnd(cnd *sqls.Cnd) (list []models.TicketNoSequence, paging *sqls.Paging) {
|
||||||
|
return repositories.TicketNoSequenceRepository.FindPageByCnd(sqls.DB(), cnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ticketNoSequenceService) Count(cnd *sqls.Cnd) int64 {
|
||||||
|
return repositories.TicketNoSequenceRepository.Count(sqls.DB(), cnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ticketNoSequenceService) Create(t *models.TicketNoSequence) error {
|
||||||
|
return repositories.TicketNoSequenceRepository.Create(sqls.DB(), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ticketNoSequenceService) Update(t *models.TicketNoSequence) error {
|
||||||
|
return repositories.TicketNoSequenceRepository.Update(sqls.DB(), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ticketNoSequenceService) Updates(id int64, columns map[string]any) error {
|
||||||
|
return repositories.TicketNoSequenceRepository.Updates(sqls.DB(), id, columns)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ticketNoSequenceService) UpdateColumn(id int64, name string, value any) error {
|
||||||
|
return repositories.TicketNoSequenceRepository.UpdateColumn(sqls.DB(), id, name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ticketNoSequenceService) Delete(id int64) {
|
||||||
|
repositories.TicketNoSequenceRepository.Delete(sqls.DB(), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ticketNoSequenceService) Next(db *gorm.DB, now time.Time) (string, error) {
|
||||||
|
dateKey := now.Format("20060102")
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
current := repositories.TicketNoSequenceRepository.GetByDateKey(db, dateKey)
|
||||||
|
if current == nil {
|
||||||
|
item := &models.TicketNoSequence{
|
||||||
|
DateKey: dateKey,
|
||||||
|
NextSeq: 2,
|
||||||
|
CreatedAt: now,
|
||||||
|
UpdatedAt: now,
|
||||||
|
}
|
||||||
|
if err := repositories.TicketNoSequenceRepository.Create(db, item); err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("TK%s%04d", dateKey, int64(1)), nil
|
||||||
|
}
|
||||||
|
seq := current.NextSeq
|
||||||
|
ok, err := repositories.TicketNoSequenceRepository.UpdateNextSeq(db, current.ID, seq, seq+1, now)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
return fmt.Sprintf("TK%s%04d", dateKey, seq), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("generate ticket number failed")
|
||||||
|
}
|
||||||
@@ -1,162 +0,0 @@
|
|||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"cs-agent/internal/models"
|
|
||||||
"cs-agent/internal/pkg/dto"
|
|
||||||
"cs-agent/internal/pkg/dto/request"
|
|
||||||
"cs-agent/internal/pkg/enums"
|
|
||||||
"cs-agent/internal/pkg/errorsx"
|
|
||||||
"cs-agent/internal/pkg/utils"
|
|
||||||
"cs-agent/internal/repositories"
|
|
||||||
|
|
||||||
"github.com/mlogclub/simple/sqls"
|
|
||||||
"github.com/mlogclub/simple/web/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
var TicketPriorityConfigService = newTicketPriorityConfigService()
|
|
||||||
|
|
||||||
func newTicketPriorityConfigService() *ticketPriorityConfigService {
|
|
||||||
return &ticketPriorityConfigService{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ticketPriorityConfigService struct{}
|
|
||||||
|
|
||||||
func (s *ticketPriorityConfigService) Get(id int64) *models.TicketPriorityConfig {
|
|
||||||
return repositories.TicketPriorityConfigRepository.Get(sqls.DB(), id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketPriorityConfigService) Take(where ...interface{}) *models.TicketPriorityConfig {
|
|
||||||
return repositories.TicketPriorityConfigRepository.Take(sqls.DB(), where...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketPriorityConfigService) Find(cnd *sqls.Cnd) []models.TicketPriorityConfig {
|
|
||||||
return repositories.TicketPriorityConfigRepository.Find(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketPriorityConfigService) FindOne(cnd *sqls.Cnd) *models.TicketPriorityConfig {
|
|
||||||
return repositories.TicketPriorityConfigRepository.FindOne(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketPriorityConfigService) FindPageByParams(queryParams *params.QueryParams) (list []models.TicketPriorityConfig, paging *sqls.Paging) {
|
|
||||||
return repositories.TicketPriorityConfigRepository.FindPageByParams(sqls.DB(), queryParams)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketPriorityConfigService) FindPageByCnd(cnd *sqls.Cnd) (list []models.TicketPriorityConfig, paging *sqls.Paging) {
|
|
||||||
return repositories.TicketPriorityConfigRepository.FindPageByCnd(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketPriorityConfigService) Create(t *models.TicketPriorityConfig) error {
|
|
||||||
return repositories.TicketPriorityConfigRepository.Create(sqls.DB(), t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketPriorityConfigService) Updates(id int64, columns map[string]any) error {
|
|
||||||
return repositories.TicketPriorityConfigRepository.Updates(sqls.DB(), id, columns)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketPriorityConfigService) GetDefaultActive() *models.TicketPriorityConfig {
|
|
||||||
return s.FindOne(sqls.NewCnd().Eq("status", enums.StatusOk).Asc("sort_no").Asc("id"))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketPriorityConfigService) CreateTicketPriorityConfig(req request.CreateTicketPriorityConfigRequest, operator *dto.AuthPrincipal) (*models.TicketPriorityConfig, error) {
|
|
||||||
if operator == nil {
|
|
||||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
|
||||||
}
|
|
||||||
item, err := s.buildPriorityConfigModel(0, req.Name, req.FirstResponseMinutes, req.ResolutionMinutes, int(req.Status), req.Remark)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
item.SortNo = s.nextSortNo()
|
|
||||||
item.AuditFields = utils.BuildAuditFields(operator)
|
|
||||||
if err := s.Create(item); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return item, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketPriorityConfigService) UpdateTicketPriorityConfig(req request.UpdateTicketPriorityConfigRequest, operator *dto.AuthPrincipal) error {
|
|
||||||
if operator == nil {
|
|
||||||
return errorsx.Unauthorized("未登录或登录已过期")
|
|
||||||
}
|
|
||||||
current := s.Get(req.ID)
|
|
||||||
if current == nil || current.Status == enums.StatusDeleted {
|
|
||||||
return errorsx.InvalidParam("工单优先级配置不存在")
|
|
||||||
}
|
|
||||||
item, err := s.buildPriorityConfigModel(req.ID, req.Name, req.FirstResponseMinutes, req.ResolutionMinutes, int(req.Status), req.Remark)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return s.Updates(req.ID, map[string]any{
|
|
||||||
"name": item.Name,
|
|
||||||
"first_response_minutes": item.FirstResponseMinutes,
|
|
||||||
"resolution_minutes": item.ResolutionMinutes,
|
|
||||||
"status": item.Status,
|
|
||||||
"remark": item.Remark,
|
|
||||||
"update_user_id": operator.UserID,
|
|
||||||
"update_user_name": operator.Username,
|
|
||||||
"updated_at": time.Now(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketPriorityConfigService) UpdateSort(ids []int64) error {
|
|
||||||
return sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
|
||||||
for i, id := range ids {
|
|
||||||
if err := repositories.TicketPriorityConfigRepository.UpdateColumn(ctx.Tx, id, "sort_no", i+1); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketPriorityConfigService) DeleteTicketPriorityConfig(id int64, operator *dto.AuthPrincipal) error {
|
|
||||||
if operator == nil {
|
|
||||||
return errorsx.Unauthorized("未登录或登录已过期")
|
|
||||||
}
|
|
||||||
current := s.Get(id)
|
|
||||||
if current == nil || current.Status == enums.StatusDeleted {
|
|
||||||
return errorsx.InvalidParam("工单优先级配置不存在")
|
|
||||||
}
|
|
||||||
if TicketService.Take("priority = ?", id) != nil {
|
|
||||||
return errorsx.Forbidden("该优先级仍有关联工单,无法删除")
|
|
||||||
}
|
|
||||||
return s.Updates(id, map[string]any{
|
|
||||||
"status": enums.StatusDeleted,
|
|
||||||
"update_user_id": operator.UserID,
|
|
||||||
"update_user_name": operator.Username,
|
|
||||||
"updated_at": time.Now(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketPriorityConfigService) buildPriorityConfigModel(id int64, name string, firstResponseMinutes, resolutionMinutes, status int, remark string) (*models.TicketPriorityConfig, error) {
|
|
||||||
name = strings.TrimSpace(name)
|
|
||||||
if name == "" {
|
|
||||||
return nil, errorsx.InvalidParam("工单优先级名称不能为空")
|
|
||||||
}
|
|
||||||
if firstResponseMinutes <= 0 || resolutionMinutes <= 0 {
|
|
||||||
return nil, errorsx.InvalidParam("SLA 时长必须大于 0")
|
|
||||||
}
|
|
||||||
if !enums.IsValidStatus(status) || status == int(enums.StatusDeleted) {
|
|
||||||
return nil, errorsx.InvalidParam("工单优先级状态不合法")
|
|
||||||
}
|
|
||||||
if exists := s.Take("name = ? AND status <> ? AND id <> ?", name, enums.StatusDeleted, id); exists != nil {
|
|
||||||
return nil, errorsx.InvalidParam("工单优先级名称已存在")
|
|
||||||
}
|
|
||||||
return &models.TicketPriorityConfig{
|
|
||||||
Name: name,
|
|
||||||
FirstResponseMinutes: firstResponseMinutes,
|
|
||||||
ResolutionMinutes: resolutionMinutes,
|
|
||||||
Status: enums.Status(status),
|
|
||||||
Remark: strings.TrimSpace(remark),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketPriorityConfigService) nextSortNo() int {
|
|
||||||
list := s.Find(sqls.NewCnd().NotEq("status", enums.StatusDeleted).Desc("sort_no").Desc("id").Limit(1))
|
|
||||||
if len(list) == 0 {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return list[0].SortNo + 1
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cs-agent/internal/models"
|
||||||
|
"cs-agent/internal/repositories"
|
||||||
|
|
||||||
|
"github.com/mlogclub/simple/sqls"
|
||||||
|
"github.com/mlogclub/simple/web/params"
|
||||||
|
)
|
||||||
|
|
||||||
|
var TicketProgressService = newTicketProgressService()
|
||||||
|
|
||||||
|
func newTicketProgressService() *ticketProgressService {
|
||||||
|
return &ticketProgressService{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ticketProgressService struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ticketProgressService) Get(id int64) *models.TicketProgress {
|
||||||
|
return repositories.TicketProgressRepository.Get(sqls.DB(), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ticketProgressService) Take(where ...any) *models.TicketProgress {
|
||||||
|
return repositories.TicketProgressRepository.Take(sqls.DB(), where...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ticketProgressService) Find(cnd *sqls.Cnd) []models.TicketProgress {
|
||||||
|
return repositories.TicketProgressRepository.Find(sqls.DB(), cnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ticketProgressService) FindOne(cnd *sqls.Cnd) *models.TicketProgress {
|
||||||
|
return repositories.TicketProgressRepository.FindOne(sqls.DB(), cnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ticketProgressService) FindPageByParams(params *params.QueryParams) (list []models.TicketProgress, paging *sqls.Paging) {
|
||||||
|
return repositories.TicketProgressRepository.FindPageByParams(sqls.DB(), params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ticketProgressService) FindPageByCnd(cnd *sqls.Cnd) (list []models.TicketProgress, paging *sqls.Paging) {
|
||||||
|
return repositories.TicketProgressRepository.FindPageByCnd(sqls.DB(), cnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ticketProgressService) Count(cnd *sqls.Cnd) int64 {
|
||||||
|
return repositories.TicketProgressRepository.Count(sqls.DB(), cnd)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ticketProgressService) Create(t *models.TicketProgress) error {
|
||||||
|
return repositories.TicketProgressRepository.Create(sqls.DB(), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ticketProgressService) Update(t *models.TicketProgress) error {
|
||||||
|
return repositories.TicketProgressRepository.Update(sqls.DB(), t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ticketProgressService) Updates(id int64, columns map[string]any) error {
|
||||||
|
return repositories.TicketProgressRepository.Updates(sqls.DB(), id, columns)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ticketProgressService) UpdateColumn(id int64, name string, value any) error {
|
||||||
|
return repositories.TicketProgressRepository.UpdateColumn(sqls.DB(), id, name, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *ticketProgressService) Delete(id int64) {
|
||||||
|
repositories.TicketProgressRepository.Delete(sqls.DB(), id)
|
||||||
|
}
|
||||||
@@ -1,212 +0,0 @@
|
|||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cs-agent/internal/models"
|
|
||||||
"cs-agent/internal/pkg/dto"
|
|
||||||
"cs-agent/internal/pkg/enums"
|
|
||||||
"cs-agent/internal/pkg/errorsx"
|
|
||||||
"cs-agent/internal/repositories"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mlogclub/simple/sqls"
|
|
||||||
"github.com/mlogclub/simple/web/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
var TicketRelationService = newTicketRelationService()
|
|
||||||
|
|
||||||
func newTicketRelationService() *ticketRelationService {
|
|
||||||
return &ticketRelationService{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ticketRelationService struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketRelationService) Get(id int64) *models.TicketRelation {
|
|
||||||
return repositories.TicketRelationRepository.Get(sqls.DB(), id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketRelationService) Take(where ...interface{}) *models.TicketRelation {
|
|
||||||
return repositories.TicketRelationRepository.Take(sqls.DB(), where...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketRelationService) Find(cnd *sqls.Cnd) []models.TicketRelation {
|
|
||||||
return repositories.TicketRelationRepository.Find(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketRelationService) FindOne(cnd *sqls.Cnd) *models.TicketRelation {
|
|
||||||
return repositories.TicketRelationRepository.FindOne(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketRelationService) FindPageByParams(params *params.QueryParams) (list []models.TicketRelation, paging *sqls.Paging) {
|
|
||||||
return repositories.TicketRelationRepository.FindPageByParams(sqls.DB(), params)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketRelationService) FindPageByCnd(cnd *sqls.Cnd) (list []models.TicketRelation, paging *sqls.Paging) {
|
|
||||||
return repositories.TicketRelationRepository.FindPageByCnd(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketRelationService) Count(cnd *sqls.Cnd) int64 {
|
|
||||||
return repositories.TicketRelationRepository.Count(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketRelationService) Create(t *models.TicketRelation) error {
|
|
||||||
return repositories.TicketRelationRepository.Create(sqls.DB(), t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketRelationService) Update(t *models.TicketRelation) error {
|
|
||||||
return repositories.TicketRelationRepository.Update(sqls.DB(), t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketRelationService) Updates(id int64, columns map[string]interface{}) error {
|
|
||||||
return repositories.TicketRelationRepository.Updates(sqls.DB(), id, columns)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketRelationService) UpdateColumn(id int64, name string, value interface{}) error {
|
|
||||||
return repositories.TicketRelationRepository.UpdateColumn(sqls.DB(), id, name, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketRelationService) Delete(id int64) {
|
|
||||||
repositories.TicketRelationRepository.Delete(sqls.DB(), id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketRelationService) AddRelation(ticketID, relatedTicketID int64, relationType enums.TicketRelationType, operator *dto.AuthPrincipal) error {
|
|
||||||
if operator == nil {
|
|
||||||
return errorsx.Unauthorized("未登录或登录已过期")
|
|
||||||
}
|
|
||||||
if ticketID <= 0 || relatedTicketID <= 0 {
|
|
||||||
return errorsx.InvalidParam("工单不存在")
|
|
||||||
}
|
|
||||||
if ticketID == relatedTicketID {
|
|
||||||
return errorsx.InvalidParam("不能关联自己")
|
|
||||||
}
|
|
||||||
if !isValidTicketRelationType(relationType) {
|
|
||||||
return errorsx.InvalidParam("关联类型不合法")
|
|
||||||
}
|
|
||||||
ticket := TicketService.Get(ticketID)
|
|
||||||
if ticket == nil {
|
|
||||||
return errorsx.InvalidParam("工单不存在")
|
|
||||||
}
|
|
||||||
relatedTicket := TicketService.Get(relatedTicketID)
|
|
||||||
if relatedTicket == nil {
|
|
||||||
return errorsx.InvalidParam("关联工单不存在")
|
|
||||||
}
|
|
||||||
if repositories.TicketRelationRepository.Take(sqls.DB(), "ticket_id = ? AND related_ticket_id = ? AND relation_type = ?", ticketID, relatedTicketID, relationType) != nil {
|
|
||||||
return errorsx.InvalidParam("该关联已存在")
|
|
||||||
}
|
|
||||||
now := time.Now()
|
|
||||||
inverseType := inverseTicketRelationType(relationType)
|
|
||||||
return sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
|
||||||
if err := repositories.TicketRelationRepository.Create(ctx.Tx, &models.TicketRelation{
|
|
||||||
TicketID: ticketID,
|
|
||||||
RelatedTicketID: relatedTicketID,
|
|
||||||
RelationType: relationType,
|
|
||||||
CreatedAt: now,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if repositories.TicketRelationRepository.Take(ctx.Tx, "ticket_id = ? AND related_ticket_id = ? AND relation_type = ?", relatedTicketID, ticketID, inverseType) == nil {
|
|
||||||
if err := repositories.TicketRelationRepository.Create(ctx.Tx, &models.TicketRelation{
|
|
||||||
TicketID: relatedTicketID,
|
|
||||||
RelatedTicketID: ticketID,
|
|
||||||
RelationType: inverseType,
|
|
||||||
CreatedAt: now,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := repositories.TicketEventLogRepository.Create(ctx.Tx, &models.TicketEventLog{
|
|
||||||
TicketID: ticketID,
|
|
||||||
EventType: enums.TicketEventTypeUpdated,
|
|
||||||
OperatorType: enums.IMSenderTypeAgent,
|
|
||||||
OperatorID: operator.UserID,
|
|
||||||
Content: "新增关联工单",
|
|
||||||
Payload: strings.TrimSpace(string(relationType) + ":" + relatedTicket.TicketNo),
|
|
||||||
CreatedAt: now,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return repositories.TicketEventLogRepository.Create(ctx.Tx, &models.TicketEventLog{
|
|
||||||
TicketID: relatedTicketID,
|
|
||||||
EventType: enums.TicketEventTypeUpdated,
|
|
||||||
OperatorType: enums.IMSenderTypeAgent,
|
|
||||||
OperatorID: operator.UserID,
|
|
||||||
Content: "新增关联工单",
|
|
||||||
Payload: strings.TrimSpace(string(inverseType) + ":" + ticket.TicketNo),
|
|
||||||
CreatedAt: now,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketRelationService) DeleteRelation(ticketID, relationID int64, operator *dto.AuthPrincipal) error {
|
|
||||||
if operator == nil {
|
|
||||||
return errorsx.Unauthorized("未登录或登录已过期")
|
|
||||||
}
|
|
||||||
relation := s.Get(relationID)
|
|
||||||
if relation == nil || relation.TicketID != ticketID {
|
|
||||||
return errorsx.InvalidParam("关联关系不存在")
|
|
||||||
}
|
|
||||||
ticket := TicketService.Get(relation.TicketID)
|
|
||||||
relatedTicket := TicketService.Get(relation.RelatedTicketID)
|
|
||||||
now := time.Now()
|
|
||||||
return sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
|
||||||
if err := repositories.TicketRelationRepository.DeleteByTicketRelation(ctx.Tx, relation.TicketID, relation.RelatedTicketID, string(relation.RelationType)); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := repositories.TicketRelationRepository.DeleteByTicketRelation(ctx.Tx, relation.RelatedTicketID, relation.TicketID, string(inverseTicketRelationType(relation.RelationType))); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if ticket != nil {
|
|
||||||
if err := repositories.TicketEventLogRepository.Create(ctx.Tx, &models.TicketEventLog{
|
|
||||||
TicketID: ticket.ID,
|
|
||||||
EventType: enums.TicketEventTypeUpdated,
|
|
||||||
OperatorType: enums.IMSenderTypeAgent,
|
|
||||||
OperatorID: operator.UserID,
|
|
||||||
Content: "移除关联工单",
|
|
||||||
Payload: strings.TrimSpace(string(relation.RelationType) + ":" + relationTicketNo(relatedTicket)),
|
|
||||||
CreatedAt: now,
|
|
||||||
}); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if relatedTicket != nil {
|
|
||||||
return repositories.TicketEventLogRepository.Create(ctx.Tx, &models.TicketEventLog{
|
|
||||||
TicketID: relatedTicket.ID,
|
|
||||||
EventType: enums.TicketEventTypeUpdated,
|
|
||||||
OperatorType: enums.IMSenderTypeAgent,
|
|
||||||
OperatorID: operator.UserID,
|
|
||||||
Content: "移除关联工单",
|
|
||||||
Payload: strings.TrimSpace(string(inverseTicketRelationType(relation.RelationType)) + ":" + relationTicketNo(ticket)),
|
|
||||||
CreatedAt: now,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func isValidTicketRelationType(relationType enums.TicketRelationType) bool {
|
|
||||||
switch relationType {
|
|
||||||
case enums.TicketRelationTypeDuplicate, enums.TicketRelationTypeRelated, enums.TicketRelationTypeParent, enums.TicketRelationTypeChild:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func inverseTicketRelationType(relationType enums.TicketRelationType) enums.TicketRelationType {
|
|
||||||
switch relationType {
|
|
||||||
case enums.TicketRelationTypeParent:
|
|
||||||
return enums.TicketRelationTypeChild
|
|
||||||
case enums.TicketRelationTypeChild:
|
|
||||||
return enums.TicketRelationTypeParent
|
|
||||||
default:
|
|
||||||
return relationType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func relationTicketNo(ticket *models.Ticket) string {
|
|
||||||
if ticket == nil {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
return ticket.TicketNo
|
|
||||||
}
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cs-agent/internal/models"
|
|
||||||
"cs-agent/internal/pkg/dto"
|
|
||||||
"cs-agent/internal/pkg/dto/request"
|
|
||||||
"cs-agent/internal/pkg/enums"
|
|
||||||
"cs-agent/internal/pkg/errorsx"
|
|
||||||
"cs-agent/internal/pkg/utils"
|
|
||||||
"cs-agent/internal/repositories"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/mlogclub/simple/sqls"
|
|
||||||
"github.com/mlogclub/simple/web/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
var TicketResolutionCodeService = newTicketResolutionCodeService()
|
|
||||||
|
|
||||||
func newTicketResolutionCodeService() *ticketResolutionCodeService {
|
|
||||||
return &ticketResolutionCodeService{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ticketResolutionCodeService struct{}
|
|
||||||
|
|
||||||
func (s *ticketResolutionCodeService) Get(id int64) *models.TicketResolutionCode {
|
|
||||||
return repositories.TicketResolutionCodeRepository.Get(sqls.DB(), id)
|
|
||||||
}
|
|
||||||
func (s *ticketResolutionCodeService) Take(where ...interface{}) *models.TicketResolutionCode {
|
|
||||||
return repositories.TicketResolutionCodeRepository.Take(sqls.DB(), where...)
|
|
||||||
}
|
|
||||||
func (s *ticketResolutionCodeService) Find(cnd *sqls.Cnd) []models.TicketResolutionCode {
|
|
||||||
return repositories.TicketResolutionCodeRepository.Find(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
func (s *ticketResolutionCodeService) FindOne(cnd *sqls.Cnd) *models.TicketResolutionCode {
|
|
||||||
return repositories.TicketResolutionCodeRepository.FindOne(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
func (s *ticketResolutionCodeService) FindPageByParams(params *params.QueryParams) (list []models.TicketResolutionCode, paging *sqls.Paging) {
|
|
||||||
return repositories.TicketResolutionCodeRepository.FindPageByParams(sqls.DB(), params)
|
|
||||||
}
|
|
||||||
func (s *ticketResolutionCodeService) FindPageByCnd(cnd *sqls.Cnd) (list []models.TicketResolutionCode, paging *sqls.Paging) {
|
|
||||||
return repositories.TicketResolutionCodeRepository.FindPageByCnd(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
func (s *ticketResolutionCodeService) Count(cnd *sqls.Cnd) int64 {
|
|
||||||
return repositories.TicketResolutionCodeRepository.Count(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
func (s *ticketResolutionCodeService) Create(t *models.TicketResolutionCode) error {
|
|
||||||
return repositories.TicketResolutionCodeRepository.Create(sqls.DB(), t)
|
|
||||||
}
|
|
||||||
func (s *ticketResolutionCodeService) Updates(id int64, columns map[string]interface{}) error {
|
|
||||||
return repositories.TicketResolutionCodeRepository.Updates(sqls.DB(), id, columns)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketResolutionCodeService) CreateTicketResolutionCode(req request.CreateTicketResolutionCodeRequest, operator *dto.AuthPrincipal) (*models.TicketResolutionCode, error) {
|
|
||||||
if operator == nil {
|
|
||||||
return nil, errorsx.Unauthorized("未登录或登录已过期")
|
|
||||||
}
|
|
||||||
item, err := s.buildResolutionCodeModel(0, req.Name, req.Code, int(req.Status), req.SortNo, req.Remark)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
item.AuditFields = utils.BuildAuditFields(operator)
|
|
||||||
if err := s.Create(item); err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return item, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketResolutionCodeService) UpdateTicketResolutionCode(req request.UpdateTicketResolutionCodeRequest, operator *dto.AuthPrincipal) error {
|
|
||||||
if operator == nil {
|
|
||||||
return errorsx.Unauthorized("未登录或登录已过期")
|
|
||||||
}
|
|
||||||
current := s.Get(req.ID)
|
|
||||||
if current == nil || current.Status == enums.StatusDeleted {
|
|
||||||
return errorsx.InvalidParam("工单解决码不存在")
|
|
||||||
}
|
|
||||||
item, err := s.buildResolutionCodeModel(req.ID, req.Name, req.Code, int(req.Status), req.SortNo, req.Remark)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return s.Updates(req.ID, map[string]any{
|
|
||||||
"name": item.Name,
|
|
||||||
"code": item.Code,
|
|
||||||
"status": item.Status,
|
|
||||||
"sort_no": item.SortNo,
|
|
||||||
"remark": item.Remark,
|
|
||||||
"update_user_id": operator.UserID,
|
|
||||||
"update_user_name": operator.Username,
|
|
||||||
"updated_at": time.Now(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketResolutionCodeService) DeleteTicketResolutionCode(id int64, operator *dto.AuthPrincipal) error {
|
|
||||||
if operator == nil {
|
|
||||||
return errorsx.Unauthorized("未登录或登录已过期")
|
|
||||||
}
|
|
||||||
current := s.Get(id)
|
|
||||||
if current == nil || current.Status == enums.StatusDeleted {
|
|
||||||
return errorsx.InvalidParam("工单解决码不存在")
|
|
||||||
}
|
|
||||||
return s.Updates(id, map[string]any{
|
|
||||||
"status": enums.StatusDeleted,
|
|
||||||
"update_user_id": operator.UserID,
|
|
||||||
"update_user_name": operator.Username,
|
|
||||||
"updated_at": time.Now(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketResolutionCodeService) buildResolutionCodeModel(id int64, name, code string, status, sortNo int, remark string) (*models.TicketResolutionCode, error) {
|
|
||||||
name = strings.TrimSpace(name)
|
|
||||||
code = strings.TrimSpace(code)
|
|
||||||
if name == "" {
|
|
||||||
return nil, errorsx.InvalidParam("工单解决码名称不能为空")
|
|
||||||
}
|
|
||||||
if code == "" {
|
|
||||||
return nil, errorsx.InvalidParam("工单解决码编码不能为空")
|
|
||||||
}
|
|
||||||
if !enums.IsValidStatus(status) || status == int(enums.StatusDeleted) {
|
|
||||||
return nil, errorsx.InvalidParam("工单解决码状态不合法")
|
|
||||||
}
|
|
||||||
if exists := s.Take("name = ? AND status <> ? AND id <> ?", name, enums.StatusDeleted, id); exists != nil {
|
|
||||||
return nil, errorsx.InvalidParam("工单解决码名称已存在")
|
|
||||||
}
|
|
||||||
if exists := s.Take("code = ? AND status <> ? AND id <> ?", code, enums.StatusDeleted, id); exists != nil {
|
|
||||||
return nil, errorsx.InvalidParam("工单解决码编码已存在")
|
|
||||||
}
|
|
||||||
return &models.TicketResolutionCode{
|
|
||||||
Name: name,
|
|
||||||
Code: code,
|
|
||||||
SortNo: sortNo,
|
|
||||||
Status: enums.Status(status),
|
|
||||||
Remark: strings.TrimSpace(remark),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
+342
-1614
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,6 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"cs-agent/internal/bootstrap"
|
"cs-agent/internal/bootstrap"
|
||||||
"cs-agent/internal/builders"
|
|
||||||
"cs-agent/internal/events"
|
"cs-agent/internal/events"
|
||||||
"cs-agent/internal/models"
|
"cs-agent/internal/models"
|
||||||
"cs-agent/internal/pkg/config"
|
"cs-agent/internal/pkg/config"
|
||||||
@@ -24,49 +23,77 @@ import (
|
|||||||
"github.com/mlogclub/simple/sqls"
|
"github.com/mlogclub/simple/sqls"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCreateTicketSetsTicketNoAndDeadlines(t *testing.T) {
|
func TestTicketLightweightStatuses(t *testing.T) {
|
||||||
setupTicketTestDB(t)
|
if !enums.IsValidTicketStatus(string(enums.TicketStatusPending)) {
|
||||||
operator := &dto.AuthPrincipal{UserID: 1, Username: "admin"}
|
t.Fatalf("pending should be valid")
|
||||||
|
|
||||||
first, err := services.TicketService.CreateTicket(createTestTicketRequest("ticket-1"), operator)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("CreateTicket() first error = %v", err)
|
|
||||||
}
|
}
|
||||||
second, err := services.TicketService.CreateTicket(createTestTicketRequest("ticket-2"), operator)
|
if !enums.IsValidTicketStatus(string(enums.TicketStatusInProgress)) {
|
||||||
if err != nil {
|
t.Fatalf("in_progress should be valid")
|
||||||
t.Fatalf("CreateTicket() second error = %v", err)
|
|
||||||
}
|
}
|
||||||
|
if !enums.IsValidTicketStatus(string(enums.TicketStatusDone)) {
|
||||||
if first.TicketNo == "" || second.TicketNo == "" {
|
t.Fatalf("done should be valid")
|
||||||
t.Fatalf("expected ticket numbers to be generated, got %q and %q", first.TicketNo, second.TicketNo)
|
|
||||||
}
|
}
|
||||||
if first.TicketNo == second.TicketNo {
|
for _, status := range []string{"new", "open", "pending_customer", "pending_internal", "resolved", "closed", "cancelled"} {
|
||||||
t.Fatalf("expected distinct ticket numbers, got %q", first.TicketNo)
|
if enums.IsValidTicketStatus(status) {
|
||||||
}
|
t.Fatalf("legacy status %s should be invalid", status)
|
||||||
if !strings.HasPrefix(first.TicketNo, "TK") {
|
}
|
||||||
t.Fatalf("expected ticket number prefix TK, got %q", first.TicketNo)
|
|
||||||
}
|
|
||||||
|
|
||||||
detail := services.TicketService.Get(first.ID)
|
|
||||||
if detail == nil {
|
|
||||||
t.Fatalf("expected ticket to exist")
|
|
||||||
}
|
|
||||||
if detail.NextReplyDeadlineAt == nil {
|
|
||||||
t.Fatalf("expected next reply deadline to be populated")
|
|
||||||
}
|
|
||||||
if detail.ResolveDeadlineAt == nil {
|
|
||||||
t.Fatalf("expected resolve deadline to be populated")
|
|
||||||
}
|
|
||||||
|
|
||||||
slaList := services.TicketSLARecordService.Find(sqls.NewCnd().Eq("ticket_id", first.ID))
|
|
||||||
if len(slaList) != 2 {
|
|
||||||
t.Fatalf("expected 2 SLA records, got %d", len(slaList))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestCreateTicketPublishesTicketCreatedEvent(t *testing.T) {
|
func TestTicketProgressModelExists(t *testing.T) {
|
||||||
|
item := models.TicketProgress{
|
||||||
|
TicketID: 12,
|
||||||
|
Content: "已电话联系客户确认问题仍存在",
|
||||||
|
AuthorID: 7,
|
||||||
|
}
|
||||||
|
if item.TicketID != 12 || item.AuthorID != 7 || item.Content == "" {
|
||||||
|
t.Fatalf("unexpected progress model: %+v", item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTicketServiceCreateTicketSetsPendingStatusAndTicketNo(t *testing.T) {
|
||||||
setupTicketTestDB(t)
|
setupTicketTestDB(t)
|
||||||
operator := &dto.AuthPrincipal{UserID: 1, Username: "admin"}
|
operator := createTestOperator(t, "creator")
|
||||||
|
customerID := createTestCustomer(t, "create-customer")
|
||||||
|
tagID := createTestTag(t, "create-tag")
|
||||||
|
|
||||||
|
created, err := services.TicketService.CreateTicket(request.CreateTicketRequest{
|
||||||
|
Title: "create ticket",
|
||||||
|
Description: "create ticket description",
|
||||||
|
CustomerID: customerID,
|
||||||
|
TagIDs: []int64{tagID},
|
||||||
|
CurrentAssigneeID: operator.UserID,
|
||||||
|
}, operator)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateTicket() error = %v", err)
|
||||||
|
}
|
||||||
|
if created.TicketNo == "" || !strings.HasPrefix(created.TicketNo, "TK") {
|
||||||
|
t.Fatalf("expected generated ticket number, got %q", created.TicketNo)
|
||||||
|
}
|
||||||
|
if created.Status != enums.TicketStatusPending {
|
||||||
|
t.Fatalf("expected pending status, got %s", created.Status)
|
||||||
|
}
|
||||||
|
if created.Source != enums.TicketSourceManual {
|
||||||
|
t.Fatalf("expected manual source, got %s", created.Source)
|
||||||
|
}
|
||||||
|
|
||||||
|
progresses := services.TicketProgressService.Find(sqls.NewCnd().Eq("ticket_id", created.ID))
|
||||||
|
if len(progresses) != 1 {
|
||||||
|
t.Fatalf("expected initial progress, got %d", len(progresses))
|
||||||
|
}
|
||||||
|
if progresses[0].Content != "创建工单" || progresses[0].AuthorID != operator.UserID {
|
||||||
|
t.Fatalf("unexpected initial progress: %+v", progresses[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
tags := services.TicketService.GetTags(created.ID)
|
||||||
|
if len(tags) != 1 || tags[0].ID != tagID {
|
||||||
|
t.Fatalf("expected ticket tag %d, got %+v", tagID, tags)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTicketServiceCreateTicketPublishesTicketCreatedEvent(t *testing.T) {
|
||||||
|
setupTicketTestDB(t)
|
||||||
|
operator := createTestOperator(t, "event-creator")
|
||||||
eventsCh := make(chan events.TicketCreatedEvent, 1)
|
eventsCh := make(chan events.TicketCreatedEvent, 1)
|
||||||
_, unsubscribe := eventbus.Subscribe(func(ctx context.Context, event events.TicketCreatedEvent) error {
|
_, unsubscribe := eventbus.Subscribe(func(ctx context.Context, event events.TicketCreatedEvent) error {
|
||||||
eventsCh <- event
|
eventsCh <- event
|
||||||
@@ -92,240 +119,153 @@ func TestCreateTicketPublishesTicketCreatedEvent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddInternalNoteAllowsMentionSameUserAcrossTickets(t *testing.T) {
|
func TestTicketServiceChangeStatusSetsHandledAt(t *testing.T) {
|
||||||
setupTicketTestDB(t)
|
setupTicketTestDB(t)
|
||||||
operator := &dto.AuthPrincipal{UserID: 1, Username: "admin"}
|
operator := createTestOperator(t, "status-operator")
|
||||||
mentionedUserID := createTestUser(t, "mentioned")
|
ticket, err := services.TicketService.CreateTicket(createTestTicketRequest("status-ticket"), operator)
|
||||||
|
|
||||||
first, err := services.TicketService.CreateTicket(createTestTicketRequest("note-ticket-1"), operator)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("CreateTicket() first error = %v", err)
|
|
||||||
}
|
|
||||||
second, err := services.TicketService.CreateTicket(createTestTicketRequest("note-ticket-2"), operator)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("CreateTicket() second error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
payload := fmt.Sprintf(`{"mentionUserIds":[%d]}`, mentionedUserID)
|
|
||||||
if _, err := services.TicketService.AddInternalNote(requestInternalNote(first.ID, payload), operator); err != nil {
|
|
||||||
t.Fatalf("AddInternalNote() first error = %v", err)
|
|
||||||
}
|
|
||||||
if _, err := services.TicketService.AddInternalNote(requestInternalNote(second.ID, payload), operator); err != nil {
|
|
||||||
t.Fatalf("AddInternalNote() second error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
mentions := services.TicketMentionService.Find(sqls.NewCnd().Eq("mentioned_user_id", mentionedUserID).Asc("id"))
|
|
||||||
if len(mentions) != 2 {
|
|
||||||
t.Fatalf("expected 2 mention records, got %d", len(mentions))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBatchAssignTicketsPublishesTicketAssignedEvents(t *testing.T) {
|
|
||||||
setupTicketTestDB(t)
|
|
||||||
operator := &dto.AuthPrincipal{UserID: 1, Username: "admin"}
|
|
||||||
teamID, assigneeID := createTestAgentProfile(t, "batch-event-assignee")
|
|
||||||
first, err := services.TicketService.CreateTicket(createTestTicketRequest("batch-event-1"), operator)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("CreateTicket() first error = %v", err)
|
|
||||||
}
|
|
||||||
second, err := services.TicketService.CreateTicket(createTestTicketRequest("batch-event-2"), operator)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("CreateTicket() second error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
eventsCh := make(chan events.TicketAssignedEvent, 2)
|
|
||||||
_, unsubscribe := eventbus.Subscribe(func(ctx context.Context, event events.TicketAssignedEvent) error {
|
|
||||||
eventsCh <- event
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
defer unsubscribe()
|
|
||||||
|
|
||||||
if err := services.TicketService.BatchAssignTickets(request.BatchAssignTicketRequest{
|
|
||||||
TicketIDs: []int64{first.ID, second.ID},
|
|
||||||
ToUserID: assigneeID,
|
|
||||||
ToTeamID: teamID,
|
|
||||||
Reason: "batch assign event",
|
|
||||||
}, operator); err != nil {
|
|
||||||
t.Fatalf("BatchAssignTickets() error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
got := map[int64]events.TicketAssignedEvent{}
|
|
||||||
for len(got) < 2 {
|
|
||||||
select {
|
|
||||||
case event := <-eventsCh:
|
|
||||||
got[event.TicketID] = event
|
|
||||||
case <-time.After(time.Second):
|
|
||||||
t.Fatalf("expected 2 ticket assigned events, got %d", len(got))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, ticketID := range []int64{first.ID, second.ID} {
|
|
||||||
event, ok := got[ticketID]
|
|
||||||
if !ok {
|
|
||||||
t.Fatalf("missing event for ticket %d", ticketID)
|
|
||||||
}
|
|
||||||
if event.ToUserID != assigneeID {
|
|
||||||
t.Fatalf("expected assignee id %d, got %d", assigneeID, event.ToUserID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBatchChangeStatusRollsBackOnFailure(t *testing.T) {
|
|
||||||
setupTicketTestDB(t)
|
|
||||||
operator := &dto.AuthPrincipal{UserID: 1, Username: "admin"}
|
|
||||||
|
|
||||||
first, err := services.TicketService.CreateTicket(createTestTicketRequest("batch-ticket"), operator)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("CreateTicket() error = %v", err)
|
t.Fatalf("CreateTicket() error = %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = services.TicketService.BatchChangeStatus(request.BatchChangeTicketStatusRequest{
|
if err := services.TicketService.ChangeStatus(request.ChangeTicketStatusRequest{
|
||||||
TicketIDs: []int64{first.ID, 999999},
|
|
||||||
Status: string(enums.TicketStatusOpen),
|
|
||||||
Reason: "batch open",
|
|
||||||
}, operator)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("expected batch change status to fail")
|
|
||||||
}
|
|
||||||
|
|
||||||
current := services.TicketService.Get(first.ID)
|
|
||||||
if current == nil {
|
|
||||||
t.Fatalf("expected ticket to exist")
|
|
||||||
}
|
|
||||||
if current.Status != enums.TicketStatusNew {
|
|
||||||
t.Fatalf("expected ticket status rollback to new, got %s", current.Status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestAssignTicketPromotesNewTicketToOpenAndSetsTeamAssignee(t *testing.T) {
|
|
||||||
setupTicketTestDB(t)
|
|
||||||
operator := &dto.AuthPrincipal{UserID: 1, Username: "admin"}
|
|
||||||
|
|
||||||
ticket, err := services.TicketService.CreateTicket(createTestTicketRequest("assign-ticket"), operator)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("CreateTicket() error = %v", err)
|
|
||||||
}
|
|
||||||
teamID, assigneeID := createTestAgentProfile(t, "assignee")
|
|
||||||
|
|
||||||
if err := services.TicketService.AssignTicket(request.AssignTicketRequest{
|
|
||||||
TicketID: ticket.ID,
|
TicketID: ticket.ID,
|
||||||
ToTeamID: teamID,
|
Status: string(enums.TicketStatusInProgress),
|
||||||
ToUserID: assigneeID,
|
|
||||||
Reason: "manual assign",
|
|
||||||
}, operator); err != nil {
|
}, operator); err != nil {
|
||||||
t.Fatalf("AssignTicket() error = %v", err)
|
t.Fatalf("ChangeStatus() in_progress error = %v", err)
|
||||||
}
|
}
|
||||||
|
inProgress := services.TicketService.Get(ticket.ID)
|
||||||
current := services.TicketService.Get(ticket.ID)
|
if inProgress == nil {
|
||||||
if current == nil {
|
|
||||||
t.Fatalf("expected ticket to exist")
|
t.Fatalf("expected ticket to exist")
|
||||||
}
|
}
|
||||||
if current.Status != enums.TicketStatusOpen {
|
if inProgress.Status != enums.TicketStatusInProgress {
|
||||||
t.Fatalf("expected assigned ticket status to be open, got %s", current.Status)
|
t.Fatalf("expected in_progress status, got %s", inProgress.Status)
|
||||||
}
|
}
|
||||||
if current.CurrentTeamID != teamID {
|
if inProgress.HandledAt != nil {
|
||||||
t.Fatalf("expected current team id %d, got %d", teamID, current.CurrentTeamID)
|
t.Fatalf("expected handled_at to remain nil before done")
|
||||||
}
|
}
|
||||||
if current.CurrentAssigneeID != assigneeID {
|
|
||||||
t.Fatalf("expected current assignee id %d, got %d", assigneeID, current.CurrentAssigneeID)
|
if err := services.TicketService.ChangeStatus(request.ChangeTicketStatusRequest{
|
||||||
|
TicketID: ticket.ID,
|
||||||
|
Status: string(enums.TicketStatusDone),
|
||||||
|
}, operator); err != nil {
|
||||||
|
t.Fatalf("ChangeStatus() done error = %v", err)
|
||||||
|
}
|
||||||
|
done := services.TicketService.Get(ticket.ID)
|
||||||
|
if done == nil || done.HandledAt == nil {
|
||||||
|
t.Fatalf("expected handled_at to be set after done, got %+v", done)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := services.TicketService.ChangeStatus(request.ChangeTicketStatusRequest{
|
||||||
|
TicketID: ticket.ID,
|
||||||
|
Status: string(enums.TicketStatusPending),
|
||||||
|
}, operator); err != nil {
|
||||||
|
t.Fatalf("ChangeStatus() pending error = %v", err)
|
||||||
|
}
|
||||||
|
pending := services.TicketService.Get(ticket.ID)
|
||||||
|
if pending == nil || pending.HandledAt != nil {
|
||||||
|
t.Fatalf("expected handled_at to be cleared away from done, got %+v", pending)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFindPageAggregateByCndBuildsWatcherAndLookupMaps(t *testing.T) {
|
func TestTicketServiceAddProgressStoresContentAndAuthor(t *testing.T) {
|
||||||
setupTicketTestDB(t)
|
setupTicketTestDB(t)
|
||||||
operator := &dto.AuthPrincipal{UserID: 1, Username: "admin"}
|
operator := createTestOperator(t, "progress-operator")
|
||||||
teamID, assigneeID := createTestAgentProfile(t, "aggregate-agent")
|
ticket, err := services.TicketService.CreateTicket(createTestTicketRequest("progress-ticket"), operator)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateTicket() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
progress, err := services.TicketService.AddProgress(request.CreateTicketProgressRequest{
|
||||||
|
TicketID: ticket.ID,
|
||||||
|
Content: "客户已确认问题复现路径",
|
||||||
|
}, operator)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AddProgress() error = %v", err)
|
||||||
|
}
|
||||||
|
if progress.ID <= 0 {
|
||||||
|
t.Fatalf("expected progress id")
|
||||||
|
}
|
||||||
|
if progress.Content != "客户已确认问题复现路径" || progress.AuthorID != operator.UserID {
|
||||||
|
t.Fatalf("unexpected progress: %+v", progress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTicketServiceSummaryCountsStaleTickets(t *testing.T) {
|
||||||
|
setupTicketTestDB(t)
|
||||||
|
operator := createTestOperator(t, "summary-operator")
|
||||||
|
mine, err := services.TicketService.CreateTicket(request.CreateTicketRequest{
|
||||||
|
Title: "mine stale ticket",
|
||||||
|
Description: "mine stale description",
|
||||||
|
CurrentAssigneeID: operator.UserID,
|
||||||
|
}, operator)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("CreateTicket() mine error = %v", err)
|
||||||
|
}
|
||||||
|
if _, err := services.TicketService.CreateTicket(createTestTicketRequest("unassigned ticket"), operator); err != nil {
|
||||||
|
t.Fatalf("CreateTicket() unassigned error = %v", err)
|
||||||
|
}
|
||||||
|
staleUpdatedAt := time.Now().Add(-48 * time.Hour)
|
||||||
|
if err := repositories.TicketRepository.Updates(sqls.DB(), mine.ID, map[string]any{
|
||||||
|
"updated_at": staleUpdatedAt,
|
||||||
|
}); err != nil {
|
||||||
|
t.Fatalf("update stale ticket error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
summary := services.TicketService.GetSummary(operator, 24)
|
||||||
|
if summary.All != 2 {
|
||||||
|
t.Fatalf("expected all count 2, got %d", summary.All)
|
||||||
|
}
|
||||||
|
if summary.Pending != 2 {
|
||||||
|
t.Fatalf("expected pending count 2, got %d", summary.Pending)
|
||||||
|
}
|
||||||
|
if summary.Mine != 1 {
|
||||||
|
t.Fatalf("expected mine count 1, got %d", summary.Mine)
|
||||||
|
}
|
||||||
|
if summary.Unassigned != 1 {
|
||||||
|
t.Fatalf("expected unassigned count 1, got %d", summary.Unassigned)
|
||||||
|
}
|
||||||
|
if summary.Stale != 1 {
|
||||||
|
t.Fatalf("expected stale count 1, got %d", summary.Stale)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTicketServiceFindPageAggregateEnrichesLookups(t *testing.T) {
|
||||||
|
setupTicketTestDB(t)
|
||||||
|
operator := createTestOperator(t, "aggregate-operator")
|
||||||
|
assignee := createTestOperator(t, "aggregate-assignee")
|
||||||
customerID := createTestCustomer(t, "aggregate-customer")
|
customerID := createTestCustomer(t, "aggregate-customer")
|
||||||
tagID := createTestTag(t, "aggregate-tag")
|
tagID := createTestTag(t, "aggregate-tag")
|
||||||
|
|
||||||
ticket, err := services.TicketService.CreateTicket(request.CreateTicketRequest{
|
ticket, err := services.TicketService.CreateTicket(request.CreateTicketRequest{
|
||||||
Title: "aggregate-ticket",
|
Title: "aggregate ticket",
|
||||||
|
Description: "aggregate description",
|
||||||
CustomerID: customerID,
|
CustomerID: customerID,
|
||||||
TagIDs: []int64{tagID},
|
TagIDs: []int64{tagID},
|
||||||
Priority: 3,
|
CurrentAssigneeID: assignee.UserID,
|
||||||
Severity: int(enums.TicketSeverityMajor),
|
|
||||||
CurrentTeamID: teamID,
|
|
||||||
CurrentAssigneeID: assigneeID,
|
|
||||||
}, operator)
|
}, operator)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("CreateTicket() error = %v", err)
|
t.Fatalf("CreateTicket() error = %v", err)
|
||||||
}
|
}
|
||||||
if err := repositories.TicketWatcherRepository.Create(sqls.DB(), &models.TicketWatcher{
|
|
||||||
TicketID: ticket.ID,
|
|
||||||
UserID: operator.UserID,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
}); err != nil {
|
|
||||||
t.Fatalf("create ticket watcher error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
aggregate, err := services.TicketService.FindPageAggregateByCnd(
|
aggregate, err := services.TicketService.FindPageAggregateByCnd(sqls.NewCnd().Eq("id", ticket.ID).Page(1, 10), operator.UserID)
|
||||||
sqls.NewCnd().Eq("id", ticket.ID).Page(1, 10),
|
|
||||||
operator.UserID,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("FindPageAggregateByCnd() error = %v", err)
|
t.Fatalf("FindPageAggregateByCnd() error = %v", err)
|
||||||
}
|
}
|
||||||
if len(aggregate.List) != 1 {
|
if len(aggregate.List) != 1 {
|
||||||
t.Fatalf("expected 1 ticket, got %d", len(aggregate.List))
|
t.Fatalf("expected 1 ticket, got %d", len(aggregate.List))
|
||||||
}
|
}
|
||||||
if _, ok := aggregate.WatchedTicketIDs[ticket.ID]; !ok {
|
|
||||||
t.Fatalf("expected watched ticket id to be populated")
|
|
||||||
}
|
|
||||||
if len(aggregate.TagsByTicketID[ticket.ID]) != 1 || aggregate.TagsByTicketID[ticket.ID][0].ID != tagID {
|
if len(aggregate.TagsByTicketID[ticket.ID]) != 1 || aggregate.TagsByTicketID[ticket.ID][0].ID != tagID {
|
||||||
t.Fatalf("expected tag lookup to be populated")
|
t.Fatalf("expected tag lookup to be populated")
|
||||||
}
|
}
|
||||||
if aggregate.Customers[customerID] == nil {
|
if aggregate.Customers[customerID] == nil {
|
||||||
t.Fatalf("expected customer lookup to be populated")
|
t.Fatalf("expected customer lookup to be populated")
|
||||||
}
|
}
|
||||||
if aggregate.Users[assigneeID] == nil {
|
if aggregate.Users[assignee.UserID] == nil {
|
||||||
t.Fatalf("expected assignee lookup to be populated")
|
t.Fatalf("expected assignee lookup to be populated")
|
||||||
}
|
}
|
||||||
if aggregate.Teams[teamID] == nil {
|
|
||||||
t.Fatalf("expected team lookup to be populated")
|
|
||||||
}
|
|
||||||
if len(aggregate.SLAByTicketID[ticket.ID]) != 2 {
|
|
||||||
t.Fatalf("expected 2 sla records for ticket, got %d", len(aggregate.SLAByTicketID[ticket.ID]))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWatchTicketAffectsSummaryAndListFilter(t *testing.T) {
|
func TestTicketServiceTicketNoNextConcurrent(t *testing.T) {
|
||||||
setupTicketTestDB(t)
|
|
||||||
operator := &dto.AuthPrincipal{UserID: createTestUser(t, "watch-operator"), Username: "watch-operator"}
|
|
||||||
|
|
||||||
ticket, err := services.TicketService.CreateTicket(createTestTicketRequest("watch-ticket"), operator)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("CreateTicket() error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := services.TicketService.WatchTicket(ticket.ID, operator); err != nil {
|
|
||||||
t.Fatalf("WatchTicket() error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
summary := services.TicketService.GetSummary(operator)
|
|
||||||
if summary.Watching != 1 {
|
|
||||||
t.Fatalf("expected watching summary to be 1, got %d", summary.Watching)
|
|
||||||
}
|
|
||||||
|
|
||||||
aggregate, err := services.TicketService.FindPageAggregateByCnd(
|
|
||||||
sqls.NewCnd().
|
|
||||||
Where("id IN (SELECT ticket_id FROM t_ticket_watcher WHERE user_id = ?)", operator.UserID).
|
|
||||||
Page(1, 10),
|
|
||||||
operator.UserID,
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("FindPageAggregateByCnd() error = %v", err)
|
|
||||||
}
|
|
||||||
if len(aggregate.List) != 1 {
|
|
||||||
t.Fatalf("expected 1 watched ticket, got %d", len(aggregate.List))
|
|
||||||
}
|
|
||||||
if aggregate.List[0].ID != ticket.ID {
|
|
||||||
t.Fatalf("expected watched ticket id %d, got %d", ticket.ID, aggregate.List[0].ID)
|
|
||||||
}
|
|
||||||
if _, ok := aggregate.WatchedTicketIDs[ticket.ID]; !ok {
|
|
||||||
t.Fatalf("expected watched ticket id to be marked in aggregate")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTicketNoServiceNextConcurrent(t *testing.T) {
|
|
||||||
setupTicketTestDB(t)
|
setupTicketTestDB(t)
|
||||||
|
|
||||||
const count = 20
|
const count = 20
|
||||||
@@ -373,402 +313,6 @@ func TestTicketNoServiceNextConcurrent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetRiskPageAggregateReturnsAccurateHighRiskTickets(t *testing.T) {
|
|
||||||
setupTicketTestDB(t)
|
|
||||||
operator := &dto.AuthPrincipal{UserID: 1, Username: "admin"}
|
|
||||||
|
|
||||||
highRisk, err := services.TicketService.CreateTicket(createTestTicketRequest("high-risk-ticket"), operator)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("CreateTicket() highRisk error = %v", err)
|
|
||||||
}
|
|
||||||
safe, err := services.TicketService.CreateTicket(createTestTicketRequest("safe-ticket"), operator)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("CreateTicket() safe error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := services.TicketService.ChangeStatus(request.ChangeTicketStatusRequest{
|
|
||||||
TicketID: highRisk.ID,
|
|
||||||
Status: string(enums.TicketStatusOpen),
|
|
||||||
Reason: "pick up high risk",
|
|
||||||
}, operator); err != nil {
|
|
||||||
t.Fatalf("ChangeStatus() highRisk open error = %v", err)
|
|
||||||
}
|
|
||||||
if err := services.TicketService.ChangeStatus(request.ChangeTicketStatusRequest{
|
|
||||||
TicketID: safe.ID,
|
|
||||||
Status: string(enums.TicketStatusOpen),
|
|
||||||
Reason: "pick up safe",
|
|
||||||
}, operator); err != nil {
|
|
||||||
t.Fatalf("ChangeStatus() safe open error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
if err := repositories.TicketRepository.Updates(sqls.DB(), highRisk.ID, map[string]any{
|
|
||||||
"resolve_deadline_at": now.Add(30 * time.Minute),
|
|
||||||
"updated_at": now,
|
|
||||||
}); err != nil {
|
|
||||||
t.Fatalf("update highRisk deadline error = %v", err)
|
|
||||||
}
|
|
||||||
if err := repositories.TicketRepository.Updates(sqls.DB(), safe.ID, map[string]any{
|
|
||||||
"resolve_deadline_at": now.Add(6 * time.Hour),
|
|
||||||
"updated_at": now,
|
|
||||||
}); err != nil {
|
|
||||||
t.Fatalf("update safe deadline error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
aggregate, err := services.TicketService.GetRiskPageAggregate("high_risk", 0, 60, 1, 10, operator.UserID)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("GetRiskPageAggregate() error = %v", err)
|
|
||||||
}
|
|
||||||
if len(aggregate.List) != 1 {
|
|
||||||
t.Fatalf("expected 1 high risk ticket, got %d", len(aggregate.List))
|
|
||||||
}
|
|
||||||
if aggregate.List[0].ID != highRisk.ID {
|
|
||||||
t.Fatalf("expected high risk ticket id %d, got %d", highRisk.ID, aggregate.List[0].ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBuildTicketDetailUsesAggregatedWatcherCollaboratorAndRelationLookups(t *testing.T) {
|
|
||||||
setupTicketTestDB(t)
|
|
||||||
operator := &dto.AuthPrincipal{UserID: 1, Username: "admin"}
|
|
||||||
teamID, assigneeID := createTestAgentProfile(t, "detail-agent")
|
|
||||||
|
|
||||||
parent, err := services.TicketService.CreateTicket(createTestTicketRequest("detail-parent"), operator)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("CreateTicket() parent error = %v", err)
|
|
||||||
}
|
|
||||||
child, err := services.TicketService.CreateTicket(request.CreateTicketRequest{
|
|
||||||
Title: "detail-child",
|
|
||||||
Priority: 1,
|
|
||||||
Severity: int(enums.TicketSeverityMinor),
|
|
||||||
CurrentTeamID: teamID,
|
|
||||||
CurrentAssigneeID: assigneeID,
|
|
||||||
}, operator)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("CreateTicket() child error = %v", err)
|
|
||||||
}
|
|
||||||
if err := repositories.TicketWatcherRepository.Create(sqls.DB(), &models.TicketWatcher{
|
|
||||||
TicketID: parent.ID,
|
|
||||||
UserID: assigneeID,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
}); err != nil {
|
|
||||||
t.Fatalf("create watcher error = %v", err)
|
|
||||||
}
|
|
||||||
if err := repositories.TicketCollaboratorRepository.Create(sqls.DB(), &models.TicketCollaborator{
|
|
||||||
TicketID: parent.ID,
|
|
||||||
UserID: assigneeID,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
}); err != nil {
|
|
||||||
t.Fatalf("create collaborator error = %v", err)
|
|
||||||
}
|
|
||||||
if err := repositories.TicketRelationRepository.Create(sqls.DB(), &models.TicketRelation{
|
|
||||||
TicketID: parent.ID,
|
|
||||||
RelatedTicketID: child.ID,
|
|
||||||
RelationType: enums.TicketRelationTypeChild,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
}); err != nil {
|
|
||||||
t.Fatalf("create relation error = %v", err)
|
|
||||||
}
|
|
||||||
if err := repositories.TicketCommentRepository.Create(sqls.DB(), &models.TicketComment{
|
|
||||||
TicketID: parent.ID,
|
|
||||||
CommentType: enums.TicketCommentTypePublicReply,
|
|
||||||
AuthorType: enums.IMSenderTypeAgent,
|
|
||||||
AuthorID: assigneeID,
|
|
||||||
ContentType: "text",
|
|
||||||
Content: "reply",
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
}); err != nil {
|
|
||||||
t.Fatalf("create comment error = %v", err)
|
|
||||||
}
|
|
||||||
if err := repositories.TicketEventLogRepository.Create(sqls.DB(), &models.TicketEventLog{
|
|
||||||
TicketID: parent.ID,
|
|
||||||
EventType: enums.TicketEventTypeAssigned,
|
|
||||||
OperatorType: enums.IMSenderTypeAgent,
|
|
||||||
OperatorID: assigneeID,
|
|
||||||
Content: "assigned",
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
}); err != nil {
|
|
||||||
t.Fatalf("create event error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
aggregate, err := services.TicketService.GetDetail(parent.ID)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("GetDetail() error = %v", err)
|
|
||||||
}
|
|
||||||
detail := builders.BuildTicketDetail(aggregate)
|
|
||||||
if detail == nil {
|
|
||||||
t.Fatalf("expected ticket detail to be built")
|
|
||||||
}
|
|
||||||
if len(detail.Watchers) != 1 || detail.Watchers[0].UserName == "" {
|
|
||||||
t.Fatalf("expected watcher user name to be populated")
|
|
||||||
}
|
|
||||||
if len(detail.Collaborators) != 1 || detail.Collaborators[0].UserName == "" || detail.Collaborators[0].TeamName == "" {
|
|
||||||
t.Fatalf("expected collaborator user and team names to be populated")
|
|
||||||
}
|
|
||||||
if len(detail.RelatedTickets) != 1 {
|
|
||||||
t.Fatalf("expected 1 related ticket, got %d", len(detail.RelatedTickets))
|
|
||||||
}
|
|
||||||
if detail.RelatedTickets[0].RelatedTicketNo == "" || detail.RelatedTickets[0].CurrentAssigneeName == "" || detail.RelatedTickets[0].CurrentTeamName == "" {
|
|
||||||
t.Fatalf("expected related ticket display fields to be populated")
|
|
||||||
}
|
|
||||||
if len(detail.Comments) != 1 || detail.Comments[0].AuthorName == "" {
|
|
||||||
t.Fatalf("expected comment author name to be populated")
|
|
||||||
}
|
|
||||||
if len(detail.Events) == 0 {
|
|
||||||
t.Fatalf("expected events to be populated")
|
|
||||||
}
|
|
||||||
hasNamedEvent := false
|
|
||||||
for i := range detail.Events {
|
|
||||||
if detail.Events[i].OperatorName != "" {
|
|
||||||
hasNamedEvent = true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !hasNamedEvent {
|
|
||||||
t.Fatalf("expected at least one event operator name to be populated")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCloseAndReopenTicketRefreshResolutionDeadline(t *testing.T) {
|
|
||||||
setupTicketTestDB(t)
|
|
||||||
operator := &dto.AuthPrincipal{UserID: 1, Username: "admin"}
|
|
||||||
|
|
||||||
ticket, err := services.TicketService.CreateTicket(createTestTicketRequest("deadline-ticket"), operator)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("CreateTicket() error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
original := services.TicketService.Get(ticket.ID)
|
|
||||||
if original == nil || original.ResolveDeadlineAt == nil {
|
|
||||||
t.Fatalf("expected initial resolve deadline to exist")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := services.TicketService.ChangeStatus(request.ChangeTicketStatusRequest{
|
|
||||||
TicketID: ticket.ID,
|
|
||||||
Status: string(enums.TicketStatusOpen),
|
|
||||||
Reason: "pick up",
|
|
||||||
}, operator); err != nil {
|
|
||||||
t.Fatalf("ChangeStatus() open error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := services.TicketService.CloseTicket(request.CloseTicketRequest{
|
|
||||||
TicketID: ticket.ID,
|
|
||||||
CloseReason: "done",
|
|
||||||
}, operator); err != nil {
|
|
||||||
t.Fatalf("CloseTicket() error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
closed := services.TicketService.Get(ticket.ID)
|
|
||||||
if closed == nil {
|
|
||||||
t.Fatalf("expected closed ticket to exist")
|
|
||||||
}
|
|
||||||
if closed.ResolveDeadlineAt != nil {
|
|
||||||
t.Fatalf("expected resolve deadline to be cleared after close")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := services.TicketService.ReopenTicket(request.ReopenTicketRequest{
|
|
||||||
TicketID: ticket.ID,
|
|
||||||
Reason: "need follow-up",
|
|
||||||
}, operator); err != nil {
|
|
||||||
t.Fatalf("ReopenTicket() error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
reopened := services.TicketService.Get(ticket.ID)
|
|
||||||
if reopened == nil {
|
|
||||||
t.Fatalf("expected reopened ticket to exist")
|
|
||||||
}
|
|
||||||
if reopened.ResolveDeadlineAt == nil {
|
|
||||||
t.Fatalf("expected resolve deadline to be restored after reopen")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestChangeStatusPendingCustomerThenOpenRefreshesSLAFields(t *testing.T) {
|
|
||||||
setupTicketTestDB(t)
|
|
||||||
operator := &dto.AuthPrincipal{UserID: 1, Username: "admin"}
|
|
||||||
|
|
||||||
ticket, err := services.TicketService.CreateTicket(createTestTicketRequest("pending-ticket"), operator)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("CreateTicket() error = %v", err)
|
|
||||||
}
|
|
||||||
if err := services.TicketService.ChangeStatus(request.ChangeTicketStatusRequest{
|
|
||||||
TicketID: ticket.ID,
|
|
||||||
Status: string(enums.TicketStatusOpen),
|
|
||||||
Reason: "pick up",
|
|
||||||
}, operator); err != nil {
|
|
||||||
t.Fatalf("ChangeStatus() open error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
beforePending := services.TicketService.Get(ticket.ID)
|
|
||||||
if beforePending == nil || beforePending.ResolveDeadlineAt == nil {
|
|
||||||
t.Fatalf("expected resolve deadline before pending")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := services.TicketService.ChangeStatus(request.ChangeTicketStatusRequest{
|
|
||||||
TicketID: ticket.ID,
|
|
||||||
Status: string(enums.TicketStatusPendingCustomer),
|
|
||||||
PendingReason: "waiting customer",
|
|
||||||
Reason: "pause for customer",
|
|
||||||
}, operator); err != nil {
|
|
||||||
t.Fatalf("ChangeStatus() pending error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
pending := services.TicketService.Get(ticket.ID)
|
|
||||||
if pending == nil {
|
|
||||||
t.Fatalf("expected pending ticket to exist")
|
|
||||||
}
|
|
||||||
if pending.Status != enums.TicketStatusPendingCustomer {
|
|
||||||
t.Fatalf("expected pending status, got %s", pending.Status)
|
|
||||||
}
|
|
||||||
if pending.PendingReason != "waiting customer" {
|
|
||||||
t.Fatalf("expected pending reason to be persisted, got %q", pending.PendingReason)
|
|
||||||
}
|
|
||||||
if pending.ResolveDeadlineAt == nil {
|
|
||||||
t.Fatalf("expected resolve deadline to remain calculable while pending")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := services.TicketService.ChangeStatus(request.ChangeTicketStatusRequest{
|
|
||||||
TicketID: ticket.ID,
|
|
||||||
Status: string(enums.TicketStatusOpen),
|
|
||||||
Reason: "customer replied",
|
|
||||||
}, operator); err != nil {
|
|
||||||
t.Fatalf("ChangeStatus() reopen error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
reopened := services.TicketService.Get(ticket.ID)
|
|
||||||
if reopened == nil {
|
|
||||||
t.Fatalf("expected reopened ticket to exist")
|
|
||||||
}
|
|
||||||
if reopened.Status != enums.TicketStatusOpen {
|
|
||||||
t.Fatalf("expected reopened status open, got %s", reopened.Status)
|
|
||||||
}
|
|
||||||
if reopened.PendingReason != "" {
|
|
||||||
t.Fatalf("expected pending reason to be cleared, got %q", reopened.PendingReason)
|
|
||||||
}
|
|
||||||
if reopened.ResolveDeadlineAt == nil {
|
|
||||||
t.Fatalf("expected resolve deadline after reopening")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCloseTicketBlockedByOpenChild(t *testing.T) {
|
|
||||||
setupTicketTestDB(t)
|
|
||||||
operator := &dto.AuthPrincipal{UserID: 1, Username: "admin"}
|
|
||||||
|
|
||||||
parent, err := services.TicketService.CreateTicket(createTestTicketRequest("parent-ticket"), operator)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("CreateTicket() parent error = %v", err)
|
|
||||||
}
|
|
||||||
child, err := services.TicketService.CreateTicket(createTestTicketRequest("child-ticket"), operator)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("CreateTicket() child error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := repositories.TicketRelationRepository.Create(sqls.DB(), &models.TicketRelation{
|
|
||||||
TicketID: parent.ID,
|
|
||||||
RelatedTicketID: child.ID,
|
|
||||||
RelationType: enums.TicketRelationTypeChild,
|
|
||||||
CreatedAt: time.Now(),
|
|
||||||
}); err != nil {
|
|
||||||
t.Fatalf("create relation error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
err = services.TicketService.CloseTicket(request.CloseTicketRequest{
|
|
||||||
TicketID: parent.ID,
|
|
||||||
CloseReason: "done",
|
|
||||||
}, operator)
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("expected close ticket to be blocked by open child")
|
|
||||||
}
|
|
||||||
|
|
||||||
current := services.TicketService.Get(parent.ID)
|
|
||||||
if current == nil {
|
|
||||||
t.Fatalf("expected parent ticket to exist")
|
|
||||||
}
|
|
||||||
if current.Status != enums.TicketStatusNew {
|
|
||||||
t.Fatalf("expected parent ticket status to remain new, got %s", current.Status)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTicketViewServiceSaveListAndDeleteOwnViews(t *testing.T) {
|
|
||||||
setupTicketTestDB(t)
|
|
||||||
operator := &dto.AuthPrincipal{UserID: createTestUser(t, "viewer"), Username: "viewer"}
|
|
||||||
|
|
||||||
created, err := services.TicketViewService.Save(request.SaveTicketViewRequest{
|
|
||||||
Name: "我的待处理",
|
|
||||||
Filters: map[string]any{
|
|
||||||
"quickView": "mine",
|
|
||||||
"statusFilter": "open",
|
|
||||||
},
|
|
||||||
}, operator)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("TicketViewService.Save() create error = %v", err)
|
|
||||||
}
|
|
||||||
if created.ID <= 0 {
|
|
||||||
t.Fatalf("expected created ticket view id")
|
|
||||||
}
|
|
||||||
|
|
||||||
updated, err := services.TicketViewService.Save(request.SaveTicketViewRequest{
|
|
||||||
ID: created.ID,
|
|
||||||
Name: "我的处理中",
|
|
||||||
Filters: map[string]any{
|
|
||||||
"quickView": "mine",
|
|
||||||
"statusFilter": "pending_internal",
|
|
||||||
},
|
|
||||||
}, operator)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("TicketViewService.Save() update error = %v", err)
|
|
||||||
}
|
|
||||||
if !strings.Contains(updated.FiltersJSON, "pending_internal") {
|
|
||||||
t.Fatalf("expected updated filters json, got %s", updated.FiltersJSON)
|
|
||||||
}
|
|
||||||
|
|
||||||
list := services.TicketViewService.ListByUser(operator.UserID)
|
|
||||||
if len(list) != 1 {
|
|
||||||
t.Fatalf("expected 1 ticket view, got %d", len(list))
|
|
||||||
}
|
|
||||||
if list[0].Name != "我的处理中" {
|
|
||||||
t.Fatalf("expected updated name, got %s", list[0].Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := services.TicketViewService.Delete(created.ID, operator); err != nil {
|
|
||||||
t.Fatalf("TicketViewService.Delete() error = %v", err)
|
|
||||||
}
|
|
||||||
if got := services.TicketViewService.ListByUser(operator.UserID); len(got) != 0 {
|
|
||||||
t.Fatalf("expected ticket views to be deleted, got %d", len(got))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestTicketViewServiceRejectsCrossUserUpdateAndDelete(t *testing.T) {
|
|
||||||
setupTicketTestDB(t)
|
|
||||||
owner := &dto.AuthPrincipal{UserID: createTestUser(t, "owner"), Username: "owner"}
|
|
||||||
other := &dto.AuthPrincipal{UserID: createTestUser(t, "other"), Username: "other"}
|
|
||||||
|
|
||||||
created, err := services.TicketViewService.Save(request.SaveTicketViewRequest{
|
|
||||||
Name: "owner-view",
|
|
||||||
Filters: map[string]any{
|
|
||||||
"quickView": "watching",
|
|
||||||
},
|
|
||||||
}, owner)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("TicketViewService.Save() create error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := services.TicketViewService.Save(request.SaveTicketViewRequest{
|
|
||||||
ID: created.ID,
|
|
||||||
Name: "hijack",
|
|
||||||
Filters: map[string]any{
|
|
||||||
"quickView": "all",
|
|
||||||
},
|
|
||||||
}, other); err == nil {
|
|
||||||
t.Fatalf("expected cross-user update to fail")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := services.TicketViewService.Delete(created.ID, other); err == nil {
|
|
||||||
t.Fatalf("expected cross-user delete to fail")
|
|
||||||
}
|
|
||||||
if got := services.TicketViewService.ListByUser(owner.UserID); len(got) != 1 {
|
|
||||||
t.Fatalf("expected owner view to remain, got %d", len(got))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func setupTicketTestDB(t *testing.T) {
|
func setupTicketTestDB(t *testing.T) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
@@ -795,32 +339,22 @@ func setupTicketTestDB(t *testing.T) {
|
|||||||
|
|
||||||
func createTestTicketRequest(title string) request.CreateTicketRequest {
|
func createTestTicketRequest(title string) request.CreateTicketRequest {
|
||||||
return request.CreateTicketRequest{
|
return request.CreateTicketRequest{
|
||||||
Title: title,
|
Title: title,
|
||||||
Priority: 1,
|
Description: title + " description",
|
||||||
Severity: int(enums.TicketSeverityMinor),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func requestInternalNote(ticketID int64, payload string) request.InternalNoteRequest {
|
func createTestOperator(t *testing.T, prefix string) *dto.AuthPrincipal {
|
||||||
return request.InternalNoteRequest{
|
t.Helper()
|
||||||
TicketID: ticketID,
|
userID := createTestUser(t, prefix)
|
||||||
ContentType: "text",
|
return &dto.AuthPrincipal{UserID: userID, Username: prefix}
|
||||||
Content: "note",
|
|
||||||
Payload: payload,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTestUser(t *testing.T, prefix string) int64 {
|
func createTestUser(t *testing.T, prefix string) int64 {
|
||||||
t.Helper()
|
|
||||||
return createTestUserWithID(t, 0, prefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
func createTestUserWithID(t *testing.T, id int64, prefix string) int64 {
|
|
||||||
t.Helper()
|
t.Helper()
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
username := fmt.Sprintf("%s_%d", prefix, now.UnixNano())
|
username := fmt.Sprintf("%s_%d", prefix, now.UnixNano())
|
||||||
user := &models.User{
|
user := &models.User{
|
||||||
ID: id,
|
|
||||||
Username: username,
|
Username: username,
|
||||||
Nickname: prefix,
|
Nickname: prefix,
|
||||||
Status: enums.StatusOk,
|
Status: enums.StatusOk,
|
||||||
@@ -839,52 +373,6 @@ func createTestUserWithID(t *testing.T, id int64, prefix string) int64 {
|
|||||||
return user.ID
|
return user.ID
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTestAgentProfile(t *testing.T, prefix string) (int64, int64) {
|
|
||||||
t.Helper()
|
|
||||||
|
|
||||||
now := time.Now()
|
|
||||||
userID := createTestUser(t, prefix)
|
|
||||||
team := &models.AgentTeam{
|
|
||||||
Name: fmt.Sprintf("%s-team-%d", prefix, now.UnixNano()),
|
|
||||||
Status: enums.StatusOk,
|
|
||||||
Description: "test team",
|
|
||||||
AuditFields: models.AuditFields{
|
|
||||||
CreatedAt: now,
|
|
||||||
CreateUserID: 1,
|
|
||||||
CreateUserName: "admin",
|
|
||||||
UpdatedAt: now,
|
|
||||||
UpdateUserID: 1,
|
|
||||||
UpdateUserName: "admin",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if err := repositories.AgentTeamRepository.Create(sqls.DB(), team); err != nil {
|
|
||||||
t.Fatalf("create agent team error = %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
profile := &models.AgentProfile{
|
|
||||||
UserID: userID,
|
|
||||||
TeamID: team.ID,
|
|
||||||
AgentCode: fmt.Sprintf("%s-code-%d", prefix, now.UnixNano()),
|
|
||||||
DisplayName: prefix,
|
|
||||||
ServiceStatus: enums.ServiceStatusIdle,
|
|
||||||
MaxConcurrentCount: 5,
|
|
||||||
AutoAssignEnabled: true,
|
|
||||||
Status: enums.StatusOk,
|
|
||||||
AuditFields: models.AuditFields{
|
|
||||||
CreatedAt: now,
|
|
||||||
CreateUserID: 1,
|
|
||||||
CreateUserName: "admin",
|
|
||||||
UpdatedAt: now,
|
|
||||||
UpdateUserID: 1,
|
|
||||||
UpdateUserName: "admin",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
if err := repositories.AgentProfileRepository.Create(sqls.DB(), profile); err != nil {
|
|
||||||
t.Fatalf("create agent profile error = %v", err)
|
|
||||||
}
|
|
||||||
return team.ID, userID
|
|
||||||
}
|
|
||||||
|
|
||||||
func createTestCustomer(t *testing.T, prefix string) int64 {
|
func createTestCustomer(t *testing.T, prefix string) int64 {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
|
|||||||
@@ -1,67 +0,0 @@
|
|||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cs-agent/internal/models"
|
|
||||||
"cs-agent/internal/repositories"
|
|
||||||
|
|
||||||
"github.com/mlogclub/simple/sqls"
|
|
||||||
"github.com/mlogclub/simple/web/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
var TicketSLARecordService = newTicketSLARecordService()
|
|
||||||
|
|
||||||
func newTicketSLARecordService() *ticketSLARecordService {
|
|
||||||
return &ticketSLARecordService{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ticketSLARecordService struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketSLARecordService) Get(id int64) *models.TicketSLARecord {
|
|
||||||
return repositories.TicketSLARecordRepository.Get(sqls.DB(), id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketSLARecordService) Take(where ...interface{}) *models.TicketSLARecord {
|
|
||||||
return repositories.TicketSLARecordRepository.Take(sqls.DB(), where...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketSLARecordService) Find(cnd *sqls.Cnd) []models.TicketSLARecord {
|
|
||||||
return repositories.TicketSLARecordRepository.Find(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketSLARecordService) FindOne(cnd *sqls.Cnd) *models.TicketSLARecord {
|
|
||||||
return repositories.TicketSLARecordRepository.FindOne(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketSLARecordService) FindPageByParams(params *params.QueryParams) (list []models.TicketSLARecord, paging *sqls.Paging) {
|
|
||||||
return repositories.TicketSLARecordRepository.FindPageByParams(sqls.DB(), params)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketSLARecordService) FindPageByCnd(cnd *sqls.Cnd) (list []models.TicketSLARecord, paging *sqls.Paging) {
|
|
||||||
return repositories.TicketSLARecordRepository.FindPageByCnd(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketSLARecordService) Count(cnd *sqls.Cnd) int64 {
|
|
||||||
return repositories.TicketSLARecordRepository.Count(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketSLARecordService) Create(t *models.TicketSLARecord) error {
|
|
||||||
return repositories.TicketSLARecordRepository.Create(sqls.DB(), t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketSLARecordService) Update(t *models.TicketSLARecord) error {
|
|
||||||
return repositories.TicketSLARecordRepository.Update(sqls.DB(), t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketSLARecordService) Updates(id int64, columns map[string]interface{}) error {
|
|
||||||
return repositories.TicketSLARecordRepository.Updates(sqls.DB(), id, columns)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketSLARecordService) UpdateColumn(id int64, name string, value interface{}) error {
|
|
||||||
return repositories.TicketSLARecordRepository.UpdateColumn(sqls.DB(), id, name, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketSLARecordService) Delete(id int64) {
|
|
||||||
repositories.TicketSLARecordRepository.Delete(sqls.DB(), id)
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
package services
|
|
||||||
|
|
||||||
import (
|
|
||||||
"cs-agent/internal/models"
|
|
||||||
"cs-agent/internal/repositories"
|
|
||||||
|
|
||||||
"github.com/mlogclub/simple/sqls"
|
|
||||||
"github.com/mlogclub/simple/web/params"
|
|
||||||
)
|
|
||||||
|
|
||||||
var TicketWatcherService = newTicketWatcherService()
|
|
||||||
|
|
||||||
func newTicketWatcherService() *ticketWatcherService {
|
|
||||||
return &ticketWatcherService{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ticketWatcherService struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketWatcherService) Get(id int64) *models.TicketWatcher {
|
|
||||||
return repositories.TicketWatcherRepository.Get(sqls.DB(), id)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketWatcherService) Take(where ...interface{}) *models.TicketWatcher {
|
|
||||||
return repositories.TicketWatcherRepository.Take(sqls.DB(), where...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketWatcherService) Find(cnd *sqls.Cnd) []models.TicketWatcher {
|
|
||||||
return repositories.TicketWatcherRepository.Find(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketWatcherService) FindOne(cnd *sqls.Cnd) *models.TicketWatcher {
|
|
||||||
return repositories.TicketWatcherRepository.FindOne(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketWatcherService) FindPageByParams(params *params.QueryParams) (list []models.TicketWatcher, paging *sqls.Paging) {
|
|
||||||
return repositories.TicketWatcherRepository.FindPageByParams(sqls.DB(), params)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketWatcherService) FindPageByCnd(cnd *sqls.Cnd) (list []models.TicketWatcher, paging *sqls.Paging) {
|
|
||||||
return repositories.TicketWatcherRepository.FindPageByCnd(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketWatcherService) Count(cnd *sqls.Cnd) int64 {
|
|
||||||
return repositories.TicketWatcherRepository.Count(sqls.DB(), cnd)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketWatcherService) Create(t *models.TicketWatcher) error {
|
|
||||||
return repositories.TicketWatcherRepository.Create(sqls.DB(), t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketWatcherService) Update(t *models.TicketWatcher) error {
|
|
||||||
return repositories.TicketWatcherRepository.Update(sqls.DB(), t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketWatcherService) Updates(id int64, columns map[string]interface{}) error {
|
|
||||||
return repositories.TicketWatcherRepository.Updates(sqls.DB(), id, columns)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketWatcherService) UpdateColumn(id int64, name string, value interface{}) error {
|
|
||||||
return repositories.TicketWatcherRepository.UpdateColumn(sqls.DB(), id, name, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *ticketWatcherService) Delete(id int64) {
|
|
||||||
repositories.TicketWatcherRepository.Delete(sqls.DB(), id)
|
|
||||||
}
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user