package service import ( "context" "fmt" "github.com/gochat/gochat/internal/model" "github.com/gochat/gochat/internal/repository" applogger "github.com/gochat/gochat/pkg/logger" pkgvalidator "github.com/gochat/gochat/pkg/validator" ) // AssignmentPolicyV2Service implements business logic for AssignmentPolicy V2 operations. // Reference: Chatwoot app/controllers/api/v1/assignment_policies_controller.rb type AssignmentPolicyV2Service struct { policyRepo *repository.AssignmentPolicyV2Repo apInboxRepo *repository.AssignmentPolicyInboxRepo } // NewAssignmentPolicyV2Service creates a new AssignmentPolicyV2 service. func NewAssignmentPolicyV2Service( policyRepo *repository.AssignmentPolicyV2Repo, apInboxRepo *repository.AssignmentPolicyInboxRepo, ) *AssignmentPolicyV2Service { return &AssignmentPolicyV2Service{policyRepo: policyRepo, apInboxRepo: apInboxRepo} } // CreatePolicyV2Request is the DTO for creating a V2 assignment policy. type CreatePolicyV2Request struct { Name string `json:"name" validate:"required"` Description string `json:"description,omitempty"` Type model.AssignmentPolicyV2Type `json:"type" validate:"required,oneof=round_robin fair best_skill_match"` } // UpdatePolicyV2Request is the DTO for updating a V2 assignment policy. type UpdatePolicyV2Request struct { Name string `json:"name,omitempty"` Description string `json:"description,omitempty"` Type model.AssignmentPolicyV2Type `json:"type,omitempty" validate:"omitempty,oneof=round_robin fair best_skill_match"` } // AddInboxRequestV2 is the DTO for adding an inbox to a V2 policy. type AddInboxRequestV2 struct { InboxID uint `json:"inbox_id" validate:"required"` } // Create creates a new assignment policy V2. func (s *AssignmentPolicyV2Service) Create(ctx context.Context, accountID uint, req *CreatePolicyV2Request) (*model.AssignmentPolicyV2, error) { if err := pkgvalidator.ValidateStruct(req); err != nil { return nil, fmt.Errorf("validation error: %w", err) } policy := &model.AssignmentPolicyV2{ AccountID: accountID, Name: req.Name, Description: req.Description, Type: req.Type, } if err := s.policyRepo.Create(ctx, policy); err != nil { return nil, fmt.Errorf("create assignment policy v2: %w", err) } applogger.L().Infof("Assignment policy V2 %d created for account %d (type=%s)", policy.ID, accountID, policy.Type) return policy, nil } // Get retrieves an assignment policy V2 by ID. func (s *AssignmentPolicyV2Service) Get(ctx context.Context, accountID, policyID uint) (*model.AssignmentPolicyV2, error) { policy, err := s.policyRepo.FindByID(ctx, policyID) if err != nil { return nil, fmt.Errorf("assignment policy v2 not found: %w", err) } if policy.AccountID != accountID { return nil, fmt.Errorf("assignment policy v2 %d does not belong to account %d", policyID, accountID) } return policy, nil } // List retrieves all assignment policies V2 for an account. func (s *AssignmentPolicyV2Service) List(ctx context.Context, accountID uint) ([]model.AssignmentPolicyV2, error) { return s.policyRepo.FindByAccount(ctx, accountID) } // Update updates an assignment policy V2. func (s *AssignmentPolicyV2Service) Update(ctx context.Context, accountID, policyID uint, req *UpdatePolicyV2Request) (*model.AssignmentPolicyV2, error) { policy, err := s.Get(ctx, accountID, policyID) if err != nil { return nil, err } if req.Name != "" { policy.Name = req.Name } if req.Description != "" { policy.Description = req.Description } if req.Type != "" { policy.Type = req.Type } if err := s.policyRepo.Update(ctx, policy); err != nil { return nil, fmt.Errorf("update assignment policy v2: %w", err) } applogger.L().Infof("Assignment policy V2 %d updated for account %d", policyID, accountID) return policy, nil } // Delete deletes an assignment policy V2 (and its inbox associations). func (s *AssignmentPolicyV2Service) Delete(ctx context.Context, accountID, policyID uint) error { policy, err := s.Get(ctx, accountID, policyID) if err != nil { return err } // Remove all inbox associations first if err := s.apInboxRepo.DeleteByAssignmentPolicy(ctx, policy.ID); err != nil { return fmt.Errorf("delete inbox associations: %w", err) } if err := s.policyRepo.Delete(ctx, policy.ID); err != nil { return fmt.Errorf("delete assignment policy v2: %w", err) } applogger.L().Infof("Assignment policy V2 %d deleted for account %d", policyID, accountID) return nil } // ListInboxes retrieves all inboxes associated with a policy. func (s *AssignmentPolicyV2Service) ListInboxes(ctx context.Context, accountID, policyID uint) ([]model.AssignmentPolicyInbox, error) { policy, err := s.Get(ctx, accountID, policyID) if err != nil { return nil, err } return s.apInboxRepo.FindByAssignmentPolicy(ctx, policy.ID) } // AddInbox associates an inbox with a policy. func (s *AssignmentPolicyV2Service) AddInbox(ctx context.Context, accountID, policyID uint, req *AddInboxRequestV2) (*model.AssignmentPolicyInbox, error) { if err := pkgvalidator.ValidateStruct(req); err != nil { return nil, fmt.Errorf("validation error: %w", err) } policy, err := s.Get(ctx, accountID, policyID) if err != nil { return nil, err } // Check if inbox already has a policy association existing, findErr := s.apInboxRepo.FindByInbox(ctx, req.InboxID) if findErr == nil && existing != nil { // Inbox already has a policy — update it to the new policy existing.AssignmentPolicyID = policy.ID if err := s.apInboxRepo.Create(ctx, existing); err != nil { return nil, fmt.Errorf("inbox already has a policy association: %w", err) } return existing, nil } apInbox := &model.AssignmentPolicyInbox{ AssignmentPolicyID: policy.ID, InboxID: req.InboxID, AccountID: accountID, } if err := s.apInboxRepo.Create(ctx, apInbox); err != nil { return nil, fmt.Errorf("add inbox to policy: %w", err) } applogger.L().Infof("Inbox %d added to assignment policy V2 %d", req.InboxID, policyID) return apInbox, nil } // RemoveInbox removes an inbox association from a policy. func (s *AssignmentPolicyV2Service) RemoveInbox(ctx context.Context, accountID, policyID, inboxID uint) error { policy, err := s.Get(ctx, accountID, policyID) if err != nil { return err } apInboxes, err := s.apInboxRepo.FindByAssignmentPolicy(ctx, policy.ID) if err != nil { return fmt.Errorf("find inbox associations: %w", err) } for _, apInbox := range apInboxes { if apInbox.InboxID == inboxID { if err := s.apInboxRepo.Delete(ctx, apInbox.ID); err != nil { return fmt.Errorf("remove inbox from policy: %w", err) } applogger.L().Infof("Inbox %d removed from assignment policy V2 %d", inboxID, policyID) return nil } } return fmt.Errorf("inbox %d not associated with policy %d", inboxID, policyID) } // GetInboxPolicy retrieves the assignment policy for a specific inbox. func (s *AssignmentPolicyV2Service) GetInboxPolicy(ctx context.Context, accountID, inboxID uint) (*model.AssignmentPolicyV2, error) { apInbox, err := s.apInboxRepo.FindByInbox(ctx, inboxID) if err != nil { return nil, fmt.Errorf("inbox %d has no assignment policy v2: %w", inboxID, err) } if apInbox.AccountID != accountID { return nil, fmt.Errorf("inbox policy does not belong to account %d", accountID) } policy, err := s.policyRepo.FindByID(ctx, apInbox.AssignmentPolicyID) if err != nil { return nil, fmt.Errorf("assignment policy v2 not found: %w", err) } return policy, nil } // SetInboxPolicy sets the assignment policy for a specific inbox. func (s *AssignmentPolicyV2Service) SetInboxPolicy(ctx context.Context, accountID, inboxID, policyID uint) (*model.AssignmentPolicyInbox, error) { policy, err := s.Get(ctx, accountID, policyID) if err != nil { return nil, err } // Check if inbox already has a policy — remove it first s.apInboxRepo.DeleteByInbox(ctx, inboxID) // ignore error (may not exist) apInbox := &model.AssignmentPolicyInbox{ AssignmentPolicyID: policy.ID, InboxID: inboxID, AccountID: accountID, } if err := s.apInboxRepo.Create(ctx, apInbox); err != nil { return nil, fmt.Errorf("set inbox policy: %w", err) } applogger.L().Infof("Inbox %d set to assignment policy V2 %d", inboxID, policyID) return apInbox, nil } // DeleteInboxPolicy removes the assignment policy association from an inbox. func (s *AssignmentPolicyV2Service) DeleteInboxPolicy(ctx context.Context, accountID, inboxID uint) error { if err := s.apInboxRepo.DeleteByInbox(ctx, inboxID); err != nil { return fmt.Errorf("delete inbox policy: %w", err) } applogger.L().Infof("Assignment policy V2 removed from inbox %d", inboxID) return nil }