feat(ticket): add customer linking functionality to tickets

This commit is contained in:
mlogclub
2026-05-03 12:40:16 +08:00
parent ac0f25f10c
commit dba175b0fa
7 changed files with 169 additions and 23 deletions
@@ -168,6 +168,21 @@ func (c *TicketController) PostUpdate() *web.JsonResult {
return web.JsonSuccess()
}
func (c *TicketController) PostLink_customer() *web.JsonResult {
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketUpdate)
if err != nil {
return web.JsonError(err)
}
req := request.LinkTicketCustomerRequest{}
if err := params.ReadJSON(c.Ctx, &req); err != nil {
return web.JsonError(err)
}
if err := services.TicketService.LinkCustomer(req.TicketID, req.CustomerID, operator); err != nil {
return web.JsonError(err)
}
return web.JsonSuccess()
}
func (c *TicketController) PostAssign() *web.JsonResult {
operator, err := services.AuthService.RequirePermission(c.Ctx, constants.PermissionTicketAssign)
if err != nil {
@@ -27,6 +27,11 @@ type UpdateTicketRequest struct {
CurrentAssigneeID int64 `json:"currentAssigneeId"`
}
type LinkTicketCustomerRequest struct {
TicketID int64 `json:"ticketId"`
CustomerID int64 `json:"customerId"`
}
type AssignTicketRequest struct {
TicketID int64 `json:"ticketId"`
ToUserID int64 `json:"toUserId"`
+31 -2
View File
@@ -230,8 +230,8 @@ func (s *ticketService) CreateTicket(req request.CreateTicketRequest, operator *
func withSQLiteTicketCreateLock(db *gorm.DB, fn func() error) error {
if db != nil && db.Dialector.Name() == "sqlite" {
ticketNoSQLiteMu.Lock()
defer ticketNoSQLiteMu.Unlock()
TicketNoSequenceService.ticketNoSQLiteMu.Lock()
defer TicketNoSequenceService.ticketNoSQLiteMu.Unlock()
}
return fn()
}
@@ -309,6 +309,35 @@ func (s *ticketService) UpdateTicket(req request.UpdateTicketRequest, operator *
})
}
func (s *ticketService) LinkCustomer(ticketID int64, customerID int64, operator *dto.AuthPrincipal) error {
if operator == nil {
return errorsx.Unauthorized("未登录或登录已过期")
}
ticket := s.Get(ticketID)
if ticket == nil {
return errorsx.InvalidParam("工单不存在")
}
if customerID <= 0 || CustomerService.Get(customerID) == nil {
return errorsx.InvalidParam("客户不存在")
}
if ticket.ConversationID > 0 {
conversation := ConversationService.Get(ticket.ConversationID)
if conversation == nil {
return errorsx.InvalidParam("会话不存在")
}
if conversation.CustomerID > 0 && conversation.CustomerID != customerID {
return errorsx.InvalidParam("会话与客户不匹配")
}
}
now := time.Now()
return repositories.TicketRepository.Updates(sqls.DB(), ticket.ID, map[string]any{
"customer_id": customerID,
"updated_at": now,
"update_user_id": operator.UserID,
"update_user_name": operator.Username,
})
}
func (s *ticketService) AssignTicket(req request.AssignTicketRequest, operator *dto.AuthPrincipal) error {
if operator == nil {
return errorsx.Unauthorized("未登录或登录已过期")
+38
View File
@@ -119,6 +119,44 @@ func TestTicketServiceCreateTicketPublishesTicketCreatedEvent(t *testing.T) {
}
}
func TestTicketServiceLinkCustomerUpdatesTicketCustomerID(t *testing.T) {
setupTicketTestDB(t)
operator := createTestOperator(t, "link-ticket-customer")
customerID := createTestCustomer(t, "link-ticket-customer")
ticket, err := services.TicketService.CreateTicket(createTestTicketRequest("link-ticket-customer"), operator)
if err != nil {
t.Fatalf("CreateTicket() error = %v", err)
}
if ticket.CustomerID != 0 {
t.Fatalf("expected ticket without customer, got %d", ticket.CustomerID)
}
if err := services.TicketService.LinkCustomer(ticket.ID, customerID, operator); err != nil {
t.Fatalf("LinkCustomer() error = %v", err)
}
updated := services.TicketService.Get(ticket.ID)
if updated == nil {
t.Fatalf("expected ticket")
}
if updated.CustomerID != customerID {
t.Fatalf("expected customer id %d, got %d", customerID, updated.CustomerID)
}
}
func TestTicketServiceLinkCustomerRejectsMissingCustomer(t *testing.T) {
setupTicketTestDB(t)
operator := createTestOperator(t, "link-ticket-missing-customer")
ticket, err := services.TicketService.CreateTicket(createTestTicketRequest("link-ticket-missing-customer"), operator)
if err != nil {
t.Fatalf("CreateTicket() error = %v", err)
}
if err := services.TicketService.LinkCustomer(ticket.ID, 999999, operator); err == nil {
t.Fatalf("expected LinkCustomer() to reject missing customer")
}
}
func TestTicketServiceChangeStatusSetsHandledAt(t *testing.T) {
setupTicketTestDB(t)
operator := createTestOperator(t, "status-operator")