refactor(ticket): contract backend ticket core

This commit is contained in:
mlogclub
2026-05-02 18:49:10 +08:00
parent b687804363
commit fce0b5709d
43 changed files with 1103 additions and 5615 deletions
-11
View File
@@ -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()
}
@@ -23,7 +23,7 @@ func TestTicketAssignedInAppNotification(t *testing.T) {
TicketNo: "TK202604280001",
Title: "退款处理",
Source: enums.TicketSourceManual,
Status: enums.TicketStatusOpen,
Status: enums.TicketStatusPending,
CurrentAssigneeID: 11,
AuditFields: models.AuditFields{
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
}
File diff suppressed because it is too large Load Diff
+174 -686
View File
@@ -10,7 +10,6 @@ import (
"time"
"cs-agent/internal/bootstrap"
"cs-agent/internal/builders"
"cs-agent/internal/events"
"cs-agent/internal/models"
"cs-agent/internal/pkg/config"
@@ -24,49 +23,77 @@ import (
"github.com/mlogclub/simple/sqls"
)
func TestCreateTicketSetsTicketNoAndDeadlines(t *testing.T) {
setupTicketTestDB(t)
operator := &dto.AuthPrincipal{UserID: 1, Username: "admin"}
first, err := services.TicketService.CreateTicket(createTestTicketRequest("ticket-1"), operator)
if err != nil {
t.Fatalf("CreateTicket() first error = %v", err)
func TestTicketLightweightStatuses(t *testing.T) {
if !enums.IsValidTicketStatus(string(enums.TicketStatusPending)) {
t.Fatalf("pending should be valid")
}
second, err := services.TicketService.CreateTicket(createTestTicketRequest("ticket-2"), operator)
if err != nil {
t.Fatalf("CreateTicket() second error = %v", err)
if !enums.IsValidTicketStatus(string(enums.TicketStatusInProgress)) {
t.Fatalf("in_progress should be valid")
}
if first.TicketNo == "" || second.TicketNo == "" {
t.Fatalf("expected ticket numbers to be generated, got %q and %q", first.TicketNo, second.TicketNo)
if !enums.IsValidTicketStatus(string(enums.TicketStatusDone)) {
t.Fatalf("done should be valid")
}
if first.TicketNo == second.TicketNo {
t.Fatalf("expected distinct ticket numbers, got %q", first.TicketNo)
}
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))
for _, status := range []string{"new", "open", "pending_customer", "pending_internal", "resolved", "closed", "cancelled"} {
if enums.IsValidTicketStatus(status) {
t.Fatalf("legacy status %s should be invalid", status)
}
}
}
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)
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)
_, unsubscribe := eventbus.Subscribe(func(ctx context.Context, event events.TicketCreatedEvent) error {
eventsCh <- event
@@ -92,240 +119,153 @@ func TestCreateTicketPublishesTicketCreatedEvent(t *testing.T) {
}
}
func TestAddInternalNoteAllowsMentionSameUserAcrossTickets(t *testing.T) {
func TestTicketServiceChangeStatusSetsHandledAt(t *testing.T) {
setupTicketTestDB(t)
operator := &dto.AuthPrincipal{UserID: 1, Username: "admin"}
mentionedUserID := createTestUser(t, "mentioned")
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)
operator := createTestOperator(t, "status-operator")
ticket, err := services.TicketService.CreateTicket(createTestTicketRequest("status-ticket"), operator)
if err != nil {
t.Fatalf("CreateTicket() error = %v", err)
}
err = services.TicketService.BatchChangeStatus(request.BatchChangeTicketStatusRequest{
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{
if err := services.TicketService.ChangeStatus(request.ChangeTicketStatusRequest{
TicketID: ticket.ID,
ToTeamID: teamID,
ToUserID: assigneeID,
Reason: "manual assign",
Status: string(enums.TicketStatusInProgress),
}, operator); err != nil {
t.Fatalf("AssignTicket() error = %v", err)
t.Fatalf("ChangeStatus() in_progress error = %v", err)
}
current := services.TicketService.Get(ticket.ID)
if current == nil {
inProgress := services.TicketService.Get(ticket.ID)
if inProgress == nil {
t.Fatalf("expected ticket to exist")
}
if current.Status != enums.TicketStatusOpen {
t.Fatalf("expected assigned ticket status to be open, got %s", current.Status)
if inProgress.Status != enums.TicketStatusInProgress {
t.Fatalf("expected in_progress status, got %s", inProgress.Status)
}
if current.CurrentTeamID != teamID {
t.Fatalf("expected current team id %d, got %d", teamID, current.CurrentTeamID)
if inProgress.HandledAt != nil {
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)
operator := &dto.AuthPrincipal{UserID: 1, Username: "admin"}
teamID, assigneeID := createTestAgentProfile(t, "aggregate-agent")
operator := createTestOperator(t, "progress-operator")
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")
tagID := createTestTag(t, "aggregate-tag")
ticket, err := services.TicketService.CreateTicket(request.CreateTicketRequest{
Title: "aggregate-ticket",
Title: "aggregate ticket",
Description: "aggregate description",
CustomerID: customerID,
TagIDs: []int64{tagID},
Priority: 3,
Severity: int(enums.TicketSeverityMajor),
CurrentTeamID: teamID,
CurrentAssigneeID: assigneeID,
CurrentAssigneeID: assignee.UserID,
}, operator)
if err != nil {
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(
sqls.NewCnd().Eq("id", ticket.ID).Page(1, 10),
operator.UserID,
)
aggregate, err := services.TicketService.FindPageAggregateByCnd(sqls.NewCnd().Eq("id", ticket.ID).Page(1, 10), operator.UserID)
if err != nil {
t.Fatalf("FindPageAggregateByCnd() error = %v", err)
}
if len(aggregate.List) != 1 {
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 {
t.Fatalf("expected tag lookup to be populated")
}
if aggregate.Customers[customerID] == nil {
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")
}
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) {
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) {
func TestTicketServiceTicketNoNextConcurrent(t *testing.T) {
setupTicketTestDB(t)
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) {
t.Helper()
@@ -795,32 +339,22 @@ func setupTicketTestDB(t *testing.T) {
func createTestTicketRequest(title string) request.CreateTicketRequest {
return request.CreateTicketRequest{
Title: title,
Priority: 1,
Severity: int(enums.TicketSeverityMinor),
Title: title,
Description: title + " description",
}
}
func requestInternalNote(ticketID int64, payload string) request.InternalNoteRequest {
return request.InternalNoteRequest{
TicketID: ticketID,
ContentType: "text",
Content: "note",
Payload: payload,
}
func createTestOperator(t *testing.T, prefix string) *dto.AuthPrincipal {
t.Helper()
userID := createTestUser(t, prefix)
return &dto.AuthPrincipal{UserID: userID, Username: prefix}
}
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()
now := time.Now()
username := fmt.Sprintf("%s_%d", prefix, now.UnixNano())
user := &models.User{
ID: id,
Username: username,
Nickname: prefix,
Status: enums.StatusOk,
@@ -839,52 +373,6 @@ func createTestUserWithID(t *testing.T, id int64, prefix string) int64 {
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 {
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)
}