refactor(ticket): contract backend ticket core
This commit is contained in:
@@ -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
|
||||
}
|
||||
+342
-1614
File diff suppressed because it is too large
Load Diff
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user