113 lines
4.2 KiB
Go
113 lines
4.2 KiB
Go
package service
|
|
|
|
// ContactMergeService handles the business logic for merging two contacts.
|
|
// Reference: Chatwoot app/actions/contact_merge_action.rb
|
|
//
|
|
// The merge operation runs in a transaction:
|
|
// 1. Validate both contacts belong to the account
|
|
// 2. Move all conversations from mergee to base
|
|
// 3. Move all messages (sender=mergee) to base
|
|
// 4. Move all contact_inboxes from mergee to base
|
|
// 5. Move all notes from mergee to base
|
|
// 6. Delete the mergee contact
|
|
// 7. Return the base contact
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"github.com/gochat/gochat/internal/model"
|
|
"github.com/gochat/gochat/internal/repository"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
var (
|
|
ErrMergeSameContact = errors.New("cannot merge the same contact")
|
|
ErrMergeBaseNotFound = errors.New("base contact not found")
|
|
ErrMergeeNotFound = errors.New("mergee contact not found")
|
|
ErrMergeNotInAccount = errors.New("contact does not belong to the account")
|
|
)
|
|
|
|
// MergeRequest represents the request to merge two contacts.
|
|
// Used by ContactHandler for JSON binding.
|
|
type MergeRequest struct {
|
|
BaseContactID uint `json:"base_contact_id" binding:"required"`
|
|
MergeeContactID uint `json:"mergee_contact_id" binding:"required"`
|
|
}
|
|
|
|
// ContactMergeService handles contact merge operations.
|
|
type ContactMergeService struct {
|
|
mergeRepo *repository.ContactMergeRepo
|
|
db *gorm.DB
|
|
}
|
|
|
|
// NewContactMergeService creates a new ContactMergeService.
|
|
func NewContactMergeService(mergeRepo *repository.ContactMergeRepo, db *gorm.DB) *ContactMergeService {
|
|
return &ContactMergeService{mergeRepo: mergeRepo, db: db}
|
|
}
|
|
|
|
// Merge performs the contact merge operation within a transaction.
|
|
// Reference: Chatwoot ContactMergeAction#perform — runs all merge steps in ActiveRecord::Base.transaction
|
|
//
|
|
// Returns the base contact after merge, or error if validation fails.
|
|
// If base_contact_id == mergee_contact_id, returns base contact immediately (Chatwoot behavior).
|
|
func (s *ContactMergeService) Merge(accountID, baseContactID, mergeeContactID uint) (*model.Contact, error) {
|
|
// Reference: Chatwoot — "return @base_contact if base_contact.id == mergee_contact.id"
|
|
if baseContactID == mergeeContactID {
|
|
return s.mergeRepo.GetBaseContact(accountID, baseContactID)
|
|
}
|
|
|
|
// Validate contacts belong to the account
|
|
if err := s.mergeRepo.ValidateContacts(accountID, baseContactID, mergeeContactID); err != nil {
|
|
if errors.Is(err, repository.ErrSameContactMerge) {
|
|
return nil, ErrMergeSameContact
|
|
}
|
|
if errors.Is(err, repository.ErrBaseContactNotFound) {
|
|
return nil, ErrMergeBaseNotFound
|
|
}
|
|
if errors.Is(err, repository.ErrMergeeContactNotFound) {
|
|
return nil, ErrMergeeNotFound
|
|
}
|
|
return nil, ErrMergeNotInAccount
|
|
}
|
|
|
|
// Run all merge operations in a transaction
|
|
err := s.db.Transaction(func(tx *gorm.DB) error {
|
|
// Use tx for all operations within the transaction
|
|
txRepo := repository.NewContactMergeRepo(tx)
|
|
|
|
if err := txRepo.MergeConversations(baseContactID, mergeeContactID); err != nil {
|
|
return err
|
|
}
|
|
if err := txRepo.MergeMessages(baseContactID, mergeeContactID); err != nil {
|
|
return err
|
|
}
|
|
if err := txRepo.MergeContactInboxes(baseContactID, mergeeContactID); err != nil {
|
|
return err
|
|
}
|
|
if err := txRepo.MergeContactNotes(baseContactID, mergeeContactID, accountID); err != nil {
|
|
return err
|
|
}
|
|
// 1:1 Chatwoot: merge_and_remove_mergee_contact
|
|
// Field priority: base_contact attributes take precedence over mergee_contact
|
|
// mergable_attribute_keys: identifier, name, email, phone_number, additional_attributes, custom_attributes
|
|
// Logic: mergee_attributes.deep_merge(base_attributes) → base_contact.update(merged)
|
|
if err := txRepo.MergeAttributesAndRemoveMergee(baseContactID, mergeeContactID, accountID); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
})
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Return the base contact after successful merge
|
|
return s.mergeRepo.GetBaseContact(accountID, baseContactID)
|
|
}
|
|
|
|
// MergeWithRequest handles a MergeRequest from the handler.
|
|
// Compatible with ContactHandler which calls: Merge(ctx, accountID, req)
|
|
func (s *ContactMergeService) MergeWithRequest(ctx context.Context, accountID uint, req MergeRequest) (*model.Contact, error) {
|
|
return s.Merge(accountID, req.BaseContactID, req.MergeeContactID)
|
|
} |