521 lines
20 KiB
Go
521 lines
20 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"time"
|
|
|
|
"gorm.io/datatypes"
|
|
"gorm.io/gorm"
|
|
|
|
"github.com/gochat/gochat/internal/model"
|
|
"github.com/gochat/gochat/internal/search"
|
|
)
|
|
|
|
// ConversationRepo implements GORM repository for Conversation.
|
|
// Reference: Chatwoot app/models/conversation.rb
|
|
type ConversationRepo struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
// DB returns the underlying gorm.DB for advanced query building.
|
|
func (r *ConversationRepo) DB() *gorm.DB {
|
|
return r.db
|
|
}
|
|
|
|
// NewConversationRepo creates a new Conversation repository.
|
|
func NewConversationRepo(db *gorm.DB) *ConversationRepo {
|
|
return &ConversationRepo{db: db}
|
|
}
|
|
|
|
// FindByID retrieves a conversation by primary key.
|
|
func (r *ConversationRepo) FindByID(ctx context.Context, id uint) (*model.Conversation, error) {
|
|
var conversation model.Conversation
|
|
err := r.db.WithContext(ctx).First(&conversation, id).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &conversation, nil
|
|
}
|
|
|
|
// FindByAccountAndID retrieves a conversation scoped to an account.
|
|
func (r *ConversationRepo) FindByAccountAndID(ctx context.Context, accountID, id uint) (*model.Conversation, error) {
|
|
var conversation model.Conversation
|
|
err := r.db.WithContext(ctx).Where("account_id = ? AND id = ?", accountID, id).First(&conversation).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &conversation, nil
|
|
}
|
|
|
|
// FindByAccountAndDisplayIDOrID retrieves a conversation using Chatwoot's
|
|
// account-scoped display_id route semantics, falling back to primary key for
|
|
// legacy GoChat data and tests that predate display_id.
|
|
func (r *ConversationRepo) FindByAccountAndDisplayIDOrID(ctx context.Context, accountID, routeID uint) (*model.Conversation, error) {
|
|
var conversation model.Conversation
|
|
err := r.db.WithContext(ctx).Where("account_id = ? AND display_id = ?", accountID, routeID).First(&conversation).Error
|
|
if err == nil {
|
|
return &conversation, nil
|
|
}
|
|
|
|
err = r.db.WithContext(ctx).Where("account_id = ? AND id = ?", accountID, routeID).First(&conversation).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &conversation, nil
|
|
}
|
|
|
|
// FindByAccount retrieves all conversations for an account.
|
|
func (r *ConversationRepo) FindByAccount(ctx context.Context, accountID uint, offset, limit int) ([]model.Conversation, int64, error) {
|
|
var conversations []model.Conversation
|
|
var total int64
|
|
|
|
countDB := r.db.WithContext(ctx).Model(&model.Conversation{}).Where("account_id = ?", accountID)
|
|
if err := countDB.Count(&total).Error; err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
err := r.db.WithContext(ctx).Where("account_id = ?", accountID).
|
|
Offset(offset).Limit(limit).Order("id DESC").
|
|
Find(&conversations).Error
|
|
return conversations, total, err
|
|
}
|
|
|
|
// FindByInbox retrieves all conversations for an inbox.
|
|
func (r *ConversationRepo) FindByInbox(ctx context.Context, accountID, inboxID uint, offset, limit int) ([]model.Conversation, int64, error) {
|
|
var conversations []model.Conversation
|
|
var total int64
|
|
|
|
countDB := r.db.WithContext(ctx).Model(&model.Conversation{}).
|
|
Where("account_id = ? AND inbox_id = ?", accountID, inboxID)
|
|
if err := countDB.Count(&total).Error; err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
err := r.db.WithContext(ctx).
|
|
Where("account_id = ? AND inbox_id = ?", accountID, inboxID).
|
|
Offset(offset).Limit(limit).Order("id DESC").
|
|
Find(&conversations).Error
|
|
return conversations, total, err
|
|
}
|
|
|
|
// FindByStatus retrieves conversations by status for an account.
|
|
func (r *ConversationRepo) FindByStatus(ctx context.Context, accountID uint, status model.ConversationStatus, offset, limit int) ([]model.Conversation, int64, error) {
|
|
var conversations []model.Conversation
|
|
var total int64
|
|
|
|
countDB := r.db.WithContext(ctx).Model(&model.Conversation{}).
|
|
Where("account_id = ? AND status = ?", accountID, status)
|
|
if err := countDB.Count(&total).Error; err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
err := r.db.WithContext(ctx).
|
|
Where("account_id = ? AND status = ?", accountID, status).
|
|
Offset(offset).Limit(limit).Order("id DESC").
|
|
Find(&conversations).Error
|
|
return conversations, total, err
|
|
}
|
|
|
|
// FindByAssignee retrieves conversations assigned to a specific agent.
|
|
func (r *ConversationRepo) FindByAssignee(ctx context.Context, accountID, assigneeID uint, offset, limit int) ([]model.Conversation, int64, error) {
|
|
var conversations []model.Conversation
|
|
var total int64
|
|
|
|
countDB := r.db.WithContext(ctx).Model(&model.Conversation{}).
|
|
Where("account_id = ? AND assignee_id = ?", accountID, assigneeID)
|
|
if err := countDB.Count(&total).Error; err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
err := r.db.WithContext(ctx).
|
|
Where("account_id = ? AND assignee_id = ?", accountID, assigneeID).
|
|
Offset(offset).Limit(limit).Order("id DESC").
|
|
Find(&conversations).Error
|
|
return conversations, total, err
|
|
}
|
|
|
|
// FindUnassigned retrieves conversations with no assignee.
|
|
func (r *ConversationRepo) FindUnassigned(ctx context.Context, accountID uint, offset, limit int) ([]model.Conversation, int64, error) {
|
|
var conversations []model.Conversation
|
|
var total int64
|
|
|
|
countDB := r.db.WithContext(ctx).Model(&model.Conversation{}).
|
|
Where("account_id = ? AND assignee_id IS NULL AND status = ?", accountID, model.ConversationStatusOpen)
|
|
if err := countDB.Count(&total).Error; err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
err := r.db.WithContext(ctx).
|
|
Where("account_id = ? AND assignee_id IS NULL AND status = ?", accountID, model.ConversationStatusOpen).
|
|
Offset(offset).Limit(limit).Order("id DESC").
|
|
Find(&conversations).Error
|
|
return conversations, total, err
|
|
}
|
|
|
|
// FindByContact retrieves conversations for a specific contact.
|
|
func (r *ConversationRepo) FindByContact(ctx context.Context, accountID, contactID uint, offset, limit int) ([]model.Conversation, int64, error) {
|
|
var conversations []model.Conversation
|
|
var total int64
|
|
|
|
countDB := r.db.WithContext(ctx).Model(&model.Conversation{}).
|
|
Where("account_id = ? AND contact_id = ?", accountID, contactID)
|
|
if err := countDB.Count(&total).Error; err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
err := r.db.WithContext(ctx).
|
|
Where("account_id = ? AND contact_id = ?", accountID, contactID).
|
|
Offset(offset).Limit(limit).Order("id DESC").
|
|
Find(&conversations).Error
|
|
return conversations, total, err
|
|
}
|
|
|
|
// FindRecentByContact retrieves the latest conversations for a contact.
|
|
// Reference: Chatwoot contacts/conversations#index limits to the latest 20 conversations ordered by last_activity_at.
|
|
func (r *ConversationRepo) FindRecentByContact(ctx context.Context, accountID, contactID uint, inboxID *uint, limit int) ([]model.Conversation, error) {
|
|
var conversations []model.Conversation
|
|
|
|
query := r.db.WithContext(ctx).
|
|
Where("account_id = ? AND contact_id = ?", accountID, contactID)
|
|
if inboxID != nil && *inboxID != 0 {
|
|
query = query.Where("inbox_id = ?", *inboxID)
|
|
}
|
|
|
|
err := query.Limit(limit).Order("last_activity_at DESC").Order("id DESC").Find(&conversations).Error
|
|
return conversations, err
|
|
}
|
|
|
|
// FindByContactIDs retrieves conversations for multiple contacts within an account.
|
|
func (r *ConversationRepo) FindByContactIDs(ctx context.Context, accountID uint, contactIDs []uint, offset, limit int) ([]model.Conversation, int64, error) {
|
|
var conversations []model.Conversation
|
|
var total int64
|
|
|
|
if len(contactIDs) == 0 {
|
|
return []model.Conversation{}, 0, nil
|
|
}
|
|
|
|
countDB := r.db.WithContext(ctx).Model(&model.Conversation{}).
|
|
Where("account_id = ? AND contact_id IN ?", accountID, contactIDs)
|
|
if err := countDB.Count(&total).Error; err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
err := r.db.WithContext(ctx).
|
|
Where("account_id = ? AND contact_id IN ?", accountID, contactIDs).
|
|
Offset(offset).Limit(limit).Order("last_activity_at DESC").Order("id DESC").
|
|
Find(&conversations).Error
|
|
return conversations, total, err
|
|
}
|
|
|
|
// FindByTeam retrieves conversations assigned to a specific team.
|
|
func (r *ConversationRepo) FindByTeam(ctx context.Context, accountID, teamID uint, offset, limit int) ([]model.Conversation, int64, error) {
|
|
var conversations []model.Conversation
|
|
var total int64
|
|
|
|
countDB := r.db.WithContext(ctx).Model(&model.Conversation{}).
|
|
Where("account_id = ? AND team_id = ?", accountID, teamID)
|
|
if err := countDB.Count(&total).Error; err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
err := r.db.WithContext(ctx).
|
|
Where("account_id = ? AND team_id = ?", accountID, teamID).
|
|
Offset(offset).Limit(limit).Order("id DESC").
|
|
Find(&conversations).Error
|
|
return conversations, total, err
|
|
}
|
|
|
|
// Search searches conversations by content within an account.
|
|
func (r *ConversationRepo) Search(ctx context.Context, accountID uint, query string, offset, limit int, searchMode search.SearchMode) ([]model.Conversation, int64, error) {
|
|
var conversations []model.Conversation
|
|
var total int64
|
|
|
|
condition := r.db.WithContext(ctx).Model(&model.Conversation{}).
|
|
Where("account_id = ?", accountID)
|
|
|
|
if query != "" {
|
|
if searchMode == search.SearchModeTrigram {
|
|
// pg_trgm fuzzy match on conversation labels
|
|
condition = condition.Where("labels % ?", query)
|
|
} else {
|
|
// ILIKE substring match (default)
|
|
likeQuery := "%" + query + "%"
|
|
condition = condition.Where("labels ILIKE ?", likeQuery)
|
|
}
|
|
}
|
|
|
|
if err := condition.Count(&total).Error; err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
err := condition.Offset(offset).Limit(limit).Order("id DESC").
|
|
Find(&conversations).Error
|
|
return conversations, total, err
|
|
}
|
|
|
|
// Create inserts a new conversation.
|
|
func (r *ConversationRepo) Create(ctx context.Context, conversation *model.Conversation) error {
|
|
if conversation.DisplayID == nil || *conversation.DisplayID == 0 {
|
|
var next uint
|
|
if err := r.db.WithContext(ctx).Model(&model.Conversation{}).
|
|
Select("COALESCE(MAX(display_id), 0) + 1").
|
|
Where("account_id = ?", conversation.AccountID).
|
|
Scan(&next).Error; err != nil {
|
|
return err
|
|
}
|
|
conversation.DisplayID = &next
|
|
}
|
|
return r.db.WithContext(ctx).Create(conversation).Error
|
|
}
|
|
|
|
// Update modifies an existing conversation.
|
|
func (r *ConversationRepo) Update(ctx context.Context, conversation *model.Conversation) error {
|
|
return r.db.WithContext(ctx).Save(conversation).Error
|
|
}
|
|
|
|
// UpdateStatus changes the conversation status.
|
|
func (r *ConversationRepo) UpdateStatus(ctx context.Context, id uint, status model.ConversationStatus) error {
|
|
return r.db.WithContext(ctx).Model(&model.Conversation{}).Where("id = ?", id).
|
|
Update("status", status).Error
|
|
}
|
|
|
|
// AssignAgent assigns a conversation to an agent.
|
|
func (r *ConversationRepo) AssignAgent(ctx context.Context, id, assigneeID uint) error {
|
|
return r.db.WithContext(ctx).Model(&model.Conversation{}).Where("id = ?", id).
|
|
Update("assignee_id", assigneeID).Error
|
|
}
|
|
|
|
// ToggleStatus toggles conversation between open/resolved.
|
|
func (r *ConversationRepo) ToggleStatus(ctx context.Context, id uint, status model.ConversationStatus) error {
|
|
return r.db.WithContext(ctx).Model(&model.Conversation{}).Where("id = ?", id).
|
|
Update("status", status).Error
|
|
}
|
|
|
|
// UpdateLabels updates the labels on a conversation.
|
|
func (r *ConversationRepo) UpdateLabels(ctx context.Context, id uint, labels string) error {
|
|
return r.db.WithContext(ctx).Model(&model.Conversation{}).Where("id = ?", id).
|
|
Update("labels", labels).Error
|
|
}
|
|
|
|
// Delete soft-deletes a conversation.
|
|
func (r *ConversationRepo) Delete(ctx context.Context, id uint) error {
|
|
return r.db.WithContext(ctx).Delete(&model.Conversation{}, id).Error
|
|
}
|
|
|
|
// CountByAccount returns total number of conversations in an account.
|
|
func (r *ConversationRepo) CountByAccount(ctx context.Context, accountID uint) (int64, error) {
|
|
var total int64
|
|
err := r.db.WithContext(ctx).Model(&model.Conversation{}).Where("account_id = ?", accountID).Count(&total).Error
|
|
return total, err
|
|
}
|
|
|
|
// Mute sets a conversation as muted.
|
|
func (r *ConversationRepo) Mute(ctx context.Context, id uint) error {
|
|
return r.db.WithContext(ctx).Model(&model.Conversation{}).Where("id = ?", id).
|
|
Update("muted", true).Error
|
|
}
|
|
|
|
// Unmute sets a conversation as unmuted.
|
|
func (r *ConversationRepo) Unmute(ctx context.Context, id uint) error {
|
|
return r.db.WithContext(ctx).Model(&model.Conversation{}).Where("id = ?", id).
|
|
Update("muted", false).Error
|
|
}
|
|
|
|
// UpdatePriority updates the priority of a conversation.
|
|
func (r *ConversationRepo) UpdatePriority(ctx context.Context, id uint, priority string) error {
|
|
return r.db.WithContext(ctx).Model(&model.Conversation{}).Where("id = ?", id).
|
|
Update("priority", priority).Error
|
|
}
|
|
|
|
// UpdateSnoozedUntil updates the snoozed_until timestamp for a conversation.
|
|
func (r *ConversationRepo) UpdateSnoozedUntil(ctx context.Context, id uint, snoozedUntil *int64) error {
|
|
return r.db.WithContext(ctx).Model(&model.Conversation{}).Where("id = ?", id).
|
|
Update("snoozed_until", snoozedUntil).Error
|
|
}
|
|
|
|
// UpdateCustomAttributes updates the custom attributes of a conversation.
|
|
func (r *ConversationRepo) UpdateCustomAttributes(ctx context.Context, id uint, attrs datatypes.JSON) error {
|
|
return r.db.WithContext(ctx).Model(&model.Conversation{}).Where("id = ?", id).
|
|
Update("custom_attributes", attrs).Error
|
|
}
|
|
|
|
// UpdateAdditionalAttributes updates the additional attributes of a conversation.
|
|
func (r *ConversationRepo) UpdateAdditionalAttributes(ctx context.Context, id uint, attrs datatypes.JSON) error {
|
|
return r.db.WithContext(ctx).Model(&model.Conversation{}).Where("id = ?", id).
|
|
Update("additional_attributes", attrs).Error
|
|
}
|
|
|
|
// UpdateTeam assigns or unassigns a team from a conversation.
|
|
func (r *ConversationRepo) UpdateTeam(ctx context.Context, id uint, teamID *uint) error {
|
|
return r.db.WithContext(ctx).Model(&model.Conversation{}).Where("id = ?", id).
|
|
Update("team_id", teamID).Error
|
|
}
|
|
|
|
// BulkUpdateStatus updates the status of multiple conversations at once.
|
|
func (r *ConversationRepo) BulkUpdateStatus(ctx context.Context, accountID uint, ids []uint, status string) error {
|
|
return r.db.WithContext(ctx).Model(&model.Conversation{}).
|
|
Where("account_id = ? AND id IN ?", accountID, ids).
|
|
Update("status", status).Error
|
|
}
|
|
|
|
// ConversationMeta holds aggregated statistics for conversations.
|
|
type ConversationMeta struct {
|
|
StatusCounts map[string]int64 `json:"status_counts"`
|
|
LabelCounts map[string]int64 `json:"label_counts"`
|
|
TotalCount int64 `json:"total_count"`
|
|
}
|
|
|
|
// GetMeta retrieves aggregated conversation metadata for an account.
|
|
// Reference: Chatwoot conversations_controller#meta
|
|
func (r *ConversationRepo) GetMeta(ctx context.Context, accountID uint) (*ConversationMeta, error) {
|
|
meta := &ConversationMeta{
|
|
StatusCounts: make(map[string]int64),
|
|
LabelCounts: make(map[string]int64),
|
|
}
|
|
|
|
// Total count
|
|
var total int64
|
|
if err := r.db.WithContext(ctx).Model(&model.Conversation{}).Where("account_id = ?", accountID).Count(&total).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
meta.TotalCount = total
|
|
|
|
// Status distribution
|
|
type statusRow struct {
|
|
Status string
|
|
Count int64
|
|
}
|
|
var statusRows []statusRow
|
|
if err := r.db.WithContext(ctx).Model(&model.Conversation{}).
|
|
Select("status, count(*) as count").
|
|
Where("account_id = ?", accountID).
|
|
Group("status").Find(&statusRows).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
for _, row := range statusRows {
|
|
meta.StatusCounts[row.Status] = row.Count
|
|
}
|
|
|
|
// Label distribution — parse comma-separated labels field
|
|
type labelRow struct {
|
|
Labels string
|
|
}
|
|
var labelRows []labelRow
|
|
if err := r.db.WithContext(ctx).Model(&model.Conversation{}).
|
|
Select("labels").
|
|
Where("account_id = ? AND labels != '' AND labels IS NOT NULL", accountID).
|
|
Find(&labelRows).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
for _, row := range labelRows {
|
|
for _, label := range strings.Split(row.Labels, ",") {
|
|
label = strings.TrimSpace(label)
|
|
if label != "" {
|
|
meta.LabelCounts[label]++
|
|
}
|
|
}
|
|
}
|
|
|
|
return meta, nil
|
|
}
|
|
|
|
// MarkUnread marks a conversation as unread by resetting agent_last_seen_at to nil.
|
|
// This is used when no incoming messages exist for the conversation.
|
|
// Reference: Chatwoot conversations_controller#unread
|
|
func (r *ConversationRepo) MarkUnread(ctx context.Context, id uint) error {
|
|
return r.db.WithContext(ctx).Model(&model.Conversation{}).Where("id = ?", id).
|
|
Update("agent_last_seen_at", nil).Error
|
|
}
|
|
|
|
// UpdateAgentLastSeenAt sets agent_last_seen_at to a specific timestamp.
|
|
// Used by MarkUnread to set it to (last_incoming_message.CreatedAt - 1 second).
|
|
func (r *ConversationRepo) UpdateAgentLastSeenAt(ctx context.Context, id uint, lastSeenAt time.Time) error {
|
|
ts := lastSeenAt.Unix()
|
|
return r.db.WithContext(ctx).Model(&model.Conversation{}).Where("id = ?", id).
|
|
Update("agent_last_seen_at", ts).Error
|
|
}
|
|
|
|
// UnreadCountResult holds unread conversation counts grouped by a key.
|
|
type UnreadCountResult struct {
|
|
Key uint `gorm:"column:key"`
|
|
Count int64 `gorm:"column:count"`
|
|
}
|
|
|
|
// UnreadCountByLabel holds unread conversation counts grouped by label name.
|
|
type UnreadCountByLabel struct {
|
|
Label string `gorm:"column:label"`
|
|
Count int64 `gorm:"column:count"`
|
|
}
|
|
|
|
// GetUnreadCountsByInbox returns the number of unread conversations per inbox
|
|
// for a given account. A conversation is unread if agent_last_seen_at < last_message_at
|
|
// or agent_last_seen_at is nil and the conversation has messages.
|
|
// Reference: Chatwoot app/services/conversations/unread_counts/counter.rb
|
|
func (r *ConversationRepo) GetUnreadCountsByInbox(ctx context.Context, accountID uint) ([]UnreadCountResult, error) {
|
|
var results []UnreadCountResult
|
|
err := r.db.WithContext(ctx).Model(&model.Conversation{}).
|
|
Select("inbox_id as key, COUNT(*) as count").
|
|
Where("account_id = ? AND status = ?", accountID, model.ConversationStatusOpen).
|
|
Where("agent_last_seen_at IS NULL OR agent_last_seen_at < last_message_at").
|
|
Group("inbox_id").
|
|
Find(&results).Error
|
|
return results, err
|
|
}
|
|
|
|
// GetUnreadCountsByTeam returns the number of unread conversations per team
|
|
// for a given account.
|
|
func (r *ConversationRepo) GetUnreadCountsByTeam(ctx context.Context, accountID uint) ([]UnreadCountResult, error) {
|
|
var results []UnreadCountResult
|
|
err := r.db.WithContext(ctx).Model(&model.Conversation{}).
|
|
Select("team_id as key, COUNT(*) as count").
|
|
Where("account_id = ? AND status = ? AND team_id IS NOT NULL", accountID, model.ConversationStatusOpen).
|
|
Where("agent_last_seen_at IS NULL OR agent_last_seen_at < last_message_at").
|
|
Group("team_id").
|
|
Find(&results).Error
|
|
return results, err
|
|
}
|
|
|
|
// GetUnreadCountsByLabel returns the number of unread conversations per label
|
|
// for a given account. Queries conversation_labels join table.
|
|
func (r *ConversationRepo) GetUnreadCountsByLabel(ctx context.Context, accountID uint) ([]UnreadCountByLabel, error) {
|
|
var results []UnreadCountByLabel
|
|
err := r.db.WithContext(ctx).
|
|
Table("conversation_labels").
|
|
Select("tags.name as label, COUNT(*) as count").
|
|
Joins("JOIN conversations ON conversations.id = conversation_labels.conversation_id").
|
|
Joins("JOIN tags ON tags.id = conversation_labels.tag_id").
|
|
Where("conversations.account_id = ? AND conversations.status = ?", accountID, model.ConversationStatusOpen).
|
|
Where("conversations.agent_last_seen_at IS NULL OR conversations.agent_last_seen_at < conversations.last_message_at").
|
|
Group("tags.name").
|
|
Find(&results).Error
|
|
return results, err
|
|
}
|
|
|
|
// AssigneeWorkloadResult 表示每个agent的open conversations计数结果
|
|
// Reference: Chatwoot assignable_agents workload计算
|
|
type AssigneeWorkloadResult struct {
|
|
AssigneeID uint `json:"assignee_id"`
|
|
Count int64 `json:"count"`
|
|
}
|
|
|
|
// CountOpenConversationsByAssignees 批量统计给定account中指定agent们的open conversations数量。
|
|
// 用于计算每个agent的workload,以支持智能分配排序。
|
|
// Reference: Chatwoot auto assignment logic — 按open conversations数量排序
|
|
func (r *ConversationRepo) CountOpenConversationsByAssignees(ctx context.Context, accountID uint, assigneeIDs []uint) ([]AssigneeWorkloadResult, error) {
|
|
if len(assigneeIDs) == 0 {
|
|
return []AssigneeWorkloadResult{}, nil
|
|
}
|
|
|
|
var results []AssigneeWorkloadResult
|
|
err := r.db.WithContext(ctx).Model(&model.Conversation{}).
|
|
Select("assignee_id, COUNT(*) as count").
|
|
Where("account_id = ? AND status = ? AND assignee_id IN ?", accountID, model.ConversationStatusOpen, assigneeIDs).
|
|
Group("assignee_id").
|
|
Find(&results).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return results, nil
|
|
}
|