refactor: update Conversation model to include CustomerName, adjust related logic and queries
This commit is contained in:
@@ -19,7 +19,7 @@ func BuildConversation(item *models.Conversation) response.ConversationResponse
|
|||||||
AIAgentID: item.AIAgentID,
|
AIAgentID: item.AIAgentID,
|
||||||
ChannelID: item.ChannelID,
|
ChannelID: item.ChannelID,
|
||||||
CustomerID: item.CustomerID,
|
CustomerID: item.CustomerID,
|
||||||
CustomerName: buildConversationCustomerName(item.CustomerID),
|
CustomerName: item.CustomerName,
|
||||||
Status: item.Status,
|
Status: item.Status,
|
||||||
ServiceMode: item.ServiceMode,
|
ServiceMode: item.ServiceMode,
|
||||||
Priority: item.Priority,
|
Priority: item.Priority,
|
||||||
@@ -62,16 +62,6 @@ func BuildConversation(item *models.Conversation) response.ConversationResponse
|
|||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildConversationCustomerName(customerID int64) string {
|
|
||||||
if customerID <= 0 {
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
if customer := services.CustomerService.Get(customerID); customer != nil {
|
|
||||||
return strings.TrimSpace(customer.Name)
|
|
||||||
}
|
|
||||||
return ""
|
|
||||||
}
|
|
||||||
|
|
||||||
func BuildParticipantResponses(conversationID int64) []response.ConversationParticipantResponse {
|
func BuildParticipantResponses(conversationID int64) []response.ConversationParticipantResponse {
|
||||||
list := services.ConversationParticipantService.Find(sqls.NewCnd().Eq("conversation_id", conversationID).Asc("id"))
|
list := services.ConversationParticipantService.Find(sqls.NewCnd().Eq("conversation_id", conversationID).Asc("id"))
|
||||||
if len(list) == 0 {
|
if len(list) == 0 {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ func (c *ConversationController) AnyList() *web.JsonResult {
|
|||||||
|
|
||||||
if keyword, _ := params.Get(c.Ctx, "keyword"); strs.IsNotBlank(keyword) {
|
if keyword, _ := params.Get(c.Ctx, "keyword"); strs.IsNotBlank(keyword) {
|
||||||
keywordLike := "%" + strings.TrimSpace(keyword) + "%"
|
keywordLike := "%" + strings.TrimSpace(keyword) + "%"
|
||||||
cnd.Where("last_message_summary LIKE ? OR customer_id IN (SELECT id FROM t_customer WHERE name LIKE ?)", keywordLike, keywordLike)
|
cnd.Where("customer_name LIKE ? OR last_message_summary LIKE ?", keywordLike, keywordLike)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 标签搜索
|
// 标签搜索
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package migration
|
||||||
|
|
||||||
|
import (
|
||||||
|
"cs-agent/internal/models"
|
||||||
|
|
||||||
|
"github.com/mlogclub/simple/sqls"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
register(6, "backfill conversation customer name", func() error {
|
||||||
|
db := sqls.DB()
|
||||||
|
if !db.Migrator().HasColumn(&models.Conversation{}, "customer_name") {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return db.Exec(`
|
||||||
|
UPDATE t_conversation
|
||||||
|
SET customer_name = (
|
||||||
|
SELECT name FROM t_customer WHERE t_customer.id = t_conversation.customer_id
|
||||||
|
)
|
||||||
|
WHERE customer_id > 0
|
||||||
|
AND (customer_name = '' OR customer_name IS NULL)
|
||||||
|
`).Error
|
||||||
|
})
|
||||||
|
}
|
||||||
+22
-21
@@ -323,27 +323,28 @@ type Tag struct {
|
|||||||
|
|
||||||
// Conversation 客服会话。
|
// Conversation 客服会话。
|
||||||
type Conversation struct {
|
type Conversation struct {
|
||||||
ID int64 `gorm:"primaryKey;autoIncrement"` // ID 为会话主键。
|
ID int64 `gorm:"primaryKey;autoIncrement"` // ID 为会话主键。
|
||||||
AIAgentID int64 `gorm:"type:bigint;not null;default:0;index"` // AIAgentID 为当前会话绑定的 AI Agent ID。
|
AIAgentID int64 `gorm:"type:bigint;not null;default:0;index"` // AIAgentID 为当前会话绑定的 AI Agent ID。
|
||||||
ChannelID int64 `gorm:"type:bigint;not null;default:0;index"` // ChannelID 为该会话来源接入渠道ID。
|
ChannelID int64 `gorm:"type:bigint;not null;default:0;index"` // ChannelID 为该会话来源接入渠道ID。
|
||||||
CustomerID int64 `gorm:"type:bigint;not null;default:0;index"` // CustomerID 为会话所属客户 ID。
|
CustomerID int64 `gorm:"type:bigint;not null;default:0;index"` // CustomerID 为会话所属客户 ID。
|
||||||
Status enums.IMConversationStatus `gorm:"type:int;not null;default:1;index"` // Status 为会话状态,如待接入、处理中、已关闭。
|
CustomerName string `gorm:"type:varchar(100);not null;default:'';index"` // CustomerName 为客户名称冗余字段,用于列表展示和搜索。
|
||||||
ServiceMode enums.IMConversationServiceMode `gorm:"type:int;not null;default:3;index"` // ServiceMode 为服务模式,如仅AI、仅人工、AI优先人工接管。
|
Status enums.IMConversationStatus `gorm:"type:int;not null;default:1;index"` // Status 为会话状态,如待接入、处理中、已关闭。
|
||||||
Priority int `gorm:"type:int;not null;default:0;index"` // Priority 为会话优先级。
|
ServiceMode enums.IMConversationServiceMode `gorm:"type:int;not null;default:3;index"` // ServiceMode 为服务模式,如仅AI、仅人工、AI优先人工接管。
|
||||||
CurrentAssigneeID int64 `gorm:"type:bigint;not null;default:0;index"` // CurrentAssigneeID 为当前接待客服ID。
|
Priority int `gorm:"type:int;not null;default:0;index"` // Priority 为会话优先级。
|
||||||
CurrentTeamID int64 `gorm:"type:bigint;not null;default:0;index"` // CurrentTeamID 为当前处理客服组ID。
|
CurrentAssigneeID int64 `gorm:"type:bigint;not null;default:0;index"` // CurrentAssigneeID 为当前接待客服ID。
|
||||||
LastMessageID int64 `gorm:"type:bigint;not null;default:0;index"` // LastMessageID 为最后一条消息ID。
|
CurrentTeamID int64 `gorm:"type:bigint;not null;default:0;index"` // CurrentTeamID 为当前处理客服组ID。
|
||||||
LastMessageAt time.Time `gorm:"type:datetime;index"` // LastMessageAt 为最后消息时间。
|
LastMessageID int64 `gorm:"type:bigint;not null;default:0;index"` // LastMessageID 为最后一条消息ID。
|
||||||
LastActiveAt time.Time `gorm:"type:datetime;index"` // LastActiveAt 为会话最近活跃时间。
|
LastMessageAt time.Time `gorm:"type:datetime;index"` // LastMessageAt 为最后消息时间。
|
||||||
LastMessageSummary string `gorm:"type:varchar(255);not null;default:''"` // LastMessageSummary 为最后一条消息摘要。
|
LastActiveAt time.Time `gorm:"type:datetime;index"` // LastActiveAt 为会话最近活跃时间。
|
||||||
CustomerUnreadCount int `gorm:"type:int;not null;default:0"` // CustomerUnreadCount 为用户侧未读数。
|
LastMessageSummary string `gorm:"type:varchar(255);not null;default:''"` // LastMessageSummary 为最后一条消息摘要。
|
||||||
AgentUnreadCount int `gorm:"type:int;not null;default:0"` // AgentUnreadCount 为客服侧未读数。
|
CustomerUnreadCount int `gorm:"type:int;not null;default:0"` // CustomerUnreadCount 为用户侧未读数。
|
||||||
HandoffAt *time.Time `gorm:"type:datetime;index"` // HandoffAt 为最近一次转人工时间。
|
AgentUnreadCount int `gorm:"type:int;not null;default:0"` // AgentUnreadCount 为客服侧未读数。
|
||||||
HandoffReason string `gorm:"type:varchar(255);not null;default:''"` // HandoffReason 为最近一次转人工原因。
|
HandoffAt *time.Time `gorm:"type:datetime;index"` // HandoffAt 为最近一次转人工时间。
|
||||||
AIReplyRounds int `gorm:"type:int;not null;default:0"` // AIReplyRounds 为当前会话内 AI 已成功回复次数。
|
HandoffReason string `gorm:"type:varchar(255);not null;default:''"` // HandoffReason 为最近一次转人工原因。
|
||||||
ClosedAt *time.Time `gorm:"type:datetime;index"` // ClosedAt 为会话关闭时间。
|
AIReplyRounds int `gorm:"type:int;not null;default:0"` // AIReplyRounds 为当前会话内 AI 已成功回复次数。
|
||||||
ClosedBy int64 `gorm:"type:bigint;not null;default:0;index"` // ClosedBy 为关闭人用户ID,访客关闭时写0。
|
ClosedAt *time.Time `gorm:"type:datetime;index"` // ClosedAt 为会话关闭时间。
|
||||||
CloseReason string `gorm:"type:varchar(255);not null;default:''"` // CloseReason 为关闭原因。
|
ClosedBy int64 `gorm:"type:bigint;not null;default:0;index"` // ClosedBy 为关闭人用户ID,访客关闭时写0。
|
||||||
|
CloseReason string `gorm:"type:varchar(255);not null;default:''"` // CloseReason 为关闭原因。
|
||||||
AuditFields
|
AuditFields
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -91,6 +91,11 @@ func (r *conversationRepository) Updates(db *gorm.DB, id int64, columns map[stri
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *conversationRepository) UpdatesByCustomerID(db *gorm.DB, customerID int64, columns map[string]interface{}) (err error) {
|
||||||
|
err = db.Model(&models.Conversation{}).Where("customer_id = ?", customerID).Updates(columns).Error
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (r *conversationRepository) UpdateColumn(db *gorm.DB, id int64, name string, value interface{}) (err error) {
|
func (r *conversationRepository) UpdateColumn(db *gorm.DB, id int64, name string, value interface{}) (err error) {
|
||||||
err = db.Model(&models.Conversation{}).Where("id = ?", id).UpdateColumn(name, value).Error
|
err = db.Model(&models.Conversation{}).Where("id = ?", id).UpdateColumn(name, value).Error
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func (s *conversationService) ListConversations(userID int64, filter request.Age
|
|||||||
if strs.IsNotBlank(keyword) {
|
if strs.IsNotBlank(keyword) {
|
||||||
keyword = strings.TrimSpace(keyword)
|
keyword = strings.TrimSpace(keyword)
|
||||||
keywordLike := "%" + keyword + "%"
|
keywordLike := "%" + keyword + "%"
|
||||||
cnd.Where("last_message_summary LIKE ? OR customer_id IN (SELECT id FROM t_customer WHERE name LIKE ?)", keywordLike, keywordLike)
|
cnd.Where("customer_name LIKE ? OR last_message_summary LIKE ?", keywordLike, keywordLike)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch filter {
|
switch filter {
|
||||||
@@ -113,8 +113,18 @@ func (s *conversationService) Create(externalInfo openidentity.ExternalInfo, cha
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
customerName := s.getCustomerName(ctx.Tx, customerID)
|
||||||
if existing := s.getLatestNotFinishedByCustomerID(ctx.Tx, customerID); existing != nil {
|
if existing := s.getLatestNotFinishedByCustomerID(ctx.Tx, customerID); existing != nil {
|
||||||
conversation = existing
|
conversation = existing
|
||||||
|
if customerName != "" && existing.CustomerName != customerName {
|
||||||
|
if err := repositories.ConversationRepository.Updates(ctx.Tx, existing.ID, map[string]any{
|
||||||
|
"customer_name": customerName,
|
||||||
|
"updated_at": time.Now(),
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
conversation.CustomerName = customerName
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
created = true
|
created = true
|
||||||
@@ -123,6 +133,7 @@ func (s *conversationService) Create(externalInfo openidentity.ExternalInfo, cha
|
|||||||
AIAgentID: aiAgentID,
|
AIAgentID: aiAgentID,
|
||||||
ChannelID: channelID,
|
ChannelID: channelID,
|
||||||
CustomerID: customerID,
|
CustomerID: customerID,
|
||||||
|
CustomerName: customerName,
|
||||||
Status: s.resolveInitialStatus(aiAgent.ServiceMode),
|
Status: s.resolveInitialStatus(aiAgent.ServiceMode),
|
||||||
ServiceMode: aiAgent.ServiceMode,
|
ServiceMode: aiAgent.ServiceMode,
|
||||||
Priority: 0,
|
Priority: 0,
|
||||||
@@ -662,7 +673,14 @@ func (s *conversationService) BuildConversationSummary(conversation *models.Conv
|
|||||||
if strings.TrimSpace(conversation.LastMessageSummary) != "" {
|
if strings.TrimSpace(conversation.LastMessageSummary) != "" {
|
||||||
return conversation.LastMessageSummary
|
return conversation.LastMessageSummary
|
||||||
}
|
}
|
||||||
if customer := CustomerService.Get(conversation.CustomerID); customer != nil {
|
return strings.TrimSpace(conversation.CustomerName)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *conversationService) getCustomerName(db *gorm.DB, customerID int64) string {
|
||||||
|
if customerID <= 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if customer := repositories.CustomerRepository.Get(db, customerID); customer != nil {
|
||||||
return strings.TrimSpace(customer.Name)
|
return strings.TrimSpace(customer.Name)
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
@@ -739,6 +757,7 @@ func (s *conversationService) LinkConversationCustomer(conversationID, customerI
|
|||||||
now := time.Now()
|
now := time.Now()
|
||||||
return repositories.ConversationRepository.Updates(ctx.Tx, conversationID, map[string]any{
|
return repositories.ConversationRepository.Updates(ctx.Tx, conversationID, map[string]any{
|
||||||
"customer_id": customerID,
|
"customer_id": customerID,
|
||||||
|
"customer_name": strings.TrimSpace(cust.Name),
|
||||||
"update_user_id": operator.UserID,
|
"update_user_id": operator.UserID,
|
||||||
"update_user_name": operator.Username,
|
"update_user_name": operator.Username,
|
||||||
"updated_at": now,
|
"updated_at": now,
|
||||||
|
|||||||
@@ -218,16 +218,22 @@ func (s *customerService) UpdateCustomer(req request.UpdateCustomerRequest, oper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return repositories.CustomerRepository.Updates(sqls.DB(), req.ID, map[string]any{
|
return sqls.WithTransaction(func(ctx *sqls.TxContext) error {
|
||||||
"name": name,
|
now := time.Now()
|
||||||
"gender": req.Gender,
|
if err := repositories.CustomerRepository.Updates(ctx.Tx, req.ID, map[string]any{
|
||||||
"company_id": req.CompanyID,
|
"name": name,
|
||||||
"primary_mobile": strings.TrimSpace(req.PrimaryMobile),
|
"gender": req.Gender,
|
||||||
"primary_email": strings.TrimSpace(req.PrimaryEmail),
|
"company_id": req.CompanyID,
|
||||||
"remark": strings.TrimSpace(req.Remark),
|
"primary_mobile": strings.TrimSpace(req.PrimaryMobile),
|
||||||
"update_user_id": operator.UserID,
|
"primary_email": strings.TrimSpace(req.PrimaryEmail),
|
||||||
"update_user_name": operator.Username,
|
"remark": strings.TrimSpace(req.Remark),
|
||||||
"updated_at": time.Now(),
|
"update_user_id": operator.UserID,
|
||||||
|
"update_user_name": operator.Username,
|
||||||
|
"updated_at": now,
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return s.syncConversationCustomerName(ctx.Tx, req.ID, name, operator, now)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -244,6 +250,21 @@ func (s *customerService) DeleteCustomer(id int64, operator dto.AuthPrincipal) e
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *customerService) syncConversationCustomerName(db *gorm.DB, customerID int64, name string, operator *dto.AuthPrincipal, now time.Time) error {
|
||||||
|
if customerID <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
updates := map[string]any{
|
||||||
|
"customer_name": strings.TrimSpace(name),
|
||||||
|
"updated_at": now,
|
||||||
|
}
|
||||||
|
if operator != nil {
|
||||||
|
updates["update_user_id"] = operator.UserID
|
||||||
|
updates["update_user_name"] = operator.Username
|
||||||
|
}
|
||||||
|
return repositories.ConversationRepository.UpdatesByCustomerID(db, customerID, updates)
|
||||||
|
}
|
||||||
|
|
||||||
func (s *customerService) UpdateStatus(id int64, status int, operator *dto.AuthPrincipal) error {
|
func (s *customerService) UpdateStatus(id int64, status int, operator *dto.AuthPrincipal) error {
|
||||||
if operator == nil {
|
if operator == nil {
|
||||||
return errorsx.Unauthorized("未登录或登录已过期")
|
return errorsx.Unauthorized("未登录或登录已过期")
|
||||||
@@ -316,6 +337,9 @@ func (s *customerService) SaveCustomerProfile(req request.SaveCustomerProfileReq
|
|||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := s.syncConversationCustomerName(ctx.Tx, customerID, name, operator, now); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
out = repositories.CustomerRepository.Get(ctx.Tx, customerID)
|
out = repositories.CustomerRepository.Get(ctx.Tx, customerID)
|
||||||
}
|
}
|
||||||
return CustomerContactService.ReplaceAllForCustomerInTx(ctx, customerID, req.Contacts, operator)
|
return CustomerContactService.ReplaceAllForCustomerInTx(ctx, customerID, req.Contacts, operator)
|
||||||
|
|||||||
Reference in New Issue
Block a user