* H-16: align takeover with channel AI workflow (#2) * feat(conversations): complete manual AI takeover * fix(conversations): align AI takeover flow with channel AI * fix(conversations): close takeover review gaps --------- Co-authored-by: Rogee <rogee@ipao.vip> * feat(shangwutong): sync customer names back to channel (#3) Co-authored-by: Rogee <rogee@ipao.vip> * fix(shangwutong): close contact sync review gaps (#4) Co-authored-by: Rogee <rogee@ipao.vip> * H-28: harden Shangwutong CID sync (#5) * fix(shangwutong): close contact sync review gaps * fix(shangwutong): harden CID sync boundaries --------- Co-authored-by: Rogee <rogee@ipao.vip> * fix(conversations): sync AI takeover exit in realtime (#6) Co-authored-by: Rogee <rogee@ipao.vip> * test(shangwutong): cover CID rename reliability (#7) Co-authored-by: Rogee <rogee@ipao.vip> * H-43: fix WEB Captain takeover E2E flow (#8) * test(shangwutong): cover CID rename reliability * H-43: fix WEB Captain takeover flow * H-48: preserve compatible provider model * H-49: make Captain takeover atomic * H-50: prevent duplicate widget initialization --------- Co-authored-by: Rogee <rogee@ipao.vip> * H-55: make Captain bindings atomic (#9) Co-authored-by: Rogee <rogee@ipao.vip> * H-60: harden Captain migration rollback and concurrency * chore(agent): baseline — uncommitted work from the local directory * H-335: add safe Captain skills and user deactivation * H-338: close auth and Captain review blockers * H-338: close assignment and session races * H-338: close assignment and websocket invalidation gaps * H-338: enforce assignment write invariants --------- Co-authored-by: Rogee <rogee@ipao.vip>
53 lines
1.8 KiB
Go
53 lines
1.8 KiB
Go
package conversationassignee
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"github.com/gochat/gochat/internal/model"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
var ErrNotEligible = errors.New("assignee is not an active agent or administrator in this account")
|
|
|
|
func Update(ctx context.Context, db *gorm.DB, accountID, conversationID, assigneeID uint, extraUpdates map[string]any) error {
|
|
updates := make(map[string]any, len(extraUpdates)+1)
|
|
for key, value := range extraUpdates {
|
|
updates[key] = value
|
|
}
|
|
updates["assignee_id"] = nil
|
|
|
|
query := db.WithContext(ctx).Model(&model.Conversation{}).
|
|
Where("conversations.id = ? AND conversations.account_id = ?", conversationID, accountID)
|
|
if assigneeID != 0 {
|
|
updates["assignee_id"] = assigneeID
|
|
query = query.Where("EXISTS (?)", eligible(db, assigneeID))
|
|
}
|
|
|
|
result := query.Updates(updates)
|
|
if result.Error != nil {
|
|
return result.Error
|
|
}
|
|
if assigneeID != 0 && result.RowsAffected != 1 {
|
|
return ErrNotEligible
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func AutoAssign(ctx context.Context, db *gorm.DB, accountID, inboxID, conversationID, assigneeID uint) (bool, error) {
|
|
result := db.WithContext(ctx).Model(&model.Conversation{}).
|
|
Where("conversations.id = ? AND conversations.account_id = ?", conversationID, accountID).
|
|
Where("conversations.inbox_id = ? AND conversations.status = ? AND conversations.assignee_id IS NULL", inboxID, model.ConversationStatusOpen).
|
|
Where("EXISTS (?)", eligible(db, assigneeID)).
|
|
Update("assignee_id", assigneeID)
|
|
return result.RowsAffected == 1, result.Error
|
|
}
|
|
|
|
func eligible(db *gorm.DB, assigneeID uint) *gorm.DB {
|
|
return db.Table("account_users").Select("1").
|
|
Joins("JOIN users ON users.id = account_users.user_id").
|
|
Where("account_users.account_id = conversations.account_id").
|
|
Where("account_users.user_id = ?", assigneeID).
|
|
Where("account_users.role IN ? AND users.active = ?", []string{"agent", "administrator"}, true)
|
|
}
|