package service import ( "context" "fmt" "github.com/gochat/gochat/internal/autoassignment" "github.com/gochat/gochat/internal/repository" applogger "github.com/gochat/gochat/pkg/logger" pkgvalidator "github.com/gochat/gochat/pkg/validator" ) // AssignmentPolicyService implements business logic for AssignmentPolicy operations. // Reference: Chatwoot app/controllers/api/v1/assignment_policies_controller.rb type AssignmentPolicyService struct { policyRepo *repository.AssignmentPolicyRepo inboxPolicyRepo *repository.InboxAssignmentPolicyRepo assignSvc *autoassignment.AssignmentService } // NewAssignmentPolicyService creates a new AssignmentPolicy service. func NewAssignmentPolicyService( policyRepo *repository.AssignmentPolicyRepo, inboxPolicyRepo *repository.InboxAssignmentPolicyRepo, assignSvc *autoassignment.AssignmentService, ) *AssignmentPolicyService { return &AssignmentPolicyService{policyRepo: policyRepo, inboxPolicyRepo: inboxPolicyRepo, assignSvc: assignSvc} } // CreatePolicyRequest is the DTO for creating an assignment policy. type CreatePolicyRequest struct { Policy string `json:"policy" validate:"required,oneof=round_robin longest_waiting lowest_load"` FairDistributionLimit int `json:"fair_distribution_limit,omitempty"` FairDistributionWindow int `json:"fair_distribution_window,omitempty"` Active *bool `json:"active,omitempty"` } // UpdatePolicyRequest is the DTO for updating an assignment policy. type UpdatePolicyRequest struct { Policy string `json:"policy,omitempty" validate:"omitempty,oneof=round_robin longest_waiting lowest_load"` FairDistributionLimit int `json:"fair_distribution_limit,omitempty"` FairDistributionWindow int `json:"fair_distribution_window,omitempty"` Active *bool `json:"active,omitempty"` } // CreateInboxPolicyRequest is the DTO for creating an inbox assignment policy. type CreateInboxPolicyRequest struct { InboxID uint `json:"inbox_id" validate:"required"` Policy string `json:"policy" validate:"required,oneof=round_robin longest_waiting lowest_load"` FairDistributionLimit int `json:"fair_distribution_limit,omitempty"` FairDistributionWindow int `json:"fair_distribution_window,omitempty"` Active *bool `json:"active,omitempty"` } // UpdateInboxPolicyRequest is the DTO for updating an inbox assignment policy. type UpdateInboxPolicyRequest struct { Policy string `json:"policy,omitempty" validate:"omitempty,oneof=round_robin longest_waiting lowest_load"` FairDistributionLimit int `json:"fair_distribution_limit,omitempty"` FairDistributionWindow int `json:"fair_distribution_window,omitempty"` Active *bool `json:"active,omitempty"` } // GetAccountPolicy retrieves the account-level assignment policy. func (s *AssignmentPolicyService) GetAccountPolicy(ctx context.Context, accountID uint) (*autoassignment.AssignmentPolicy, error) { policy, err := s.policyRepo.FindByAccount(ctx, accountID) if err != nil { return nil, fmt.Errorf("assignment policy not found: %w", err) } return policy, nil } // CreateAccountPolicy creates the account-level assignment policy. func (s *AssignmentPolicyService) CreateAccountPolicy(ctx context.Context, accountID uint, req CreatePolicyRequest) (*autoassignment.AssignmentPolicy, error) { if err := pkgvalidator.ValidateStruct(req); err != nil { return nil, err } active := true if req.Active != nil { active = *req.Active } limit := 5 if req.FairDistributionLimit > 0 { limit = req.FairDistributionLimit } window := 300 if req.FairDistributionWindow > 0 { window = req.FairDistributionWindow } policy := &autoassignment.AssignmentPolicy{ AccountID: accountID, Policy: autoassignment.AssignmentPolicyType(req.Policy), FairDistributionLimit: limit, FairDistributionWindow: window, Active: active, } if err := s.policyRepo.Create(ctx, policy); err != nil { applogger.L().Errorf("failed to create assignment policy: %v", err) return nil, fmt.Errorf("failed to create assignment policy: %w", err) } return policy, nil } // UpdateAccountPolicy updates the account-level assignment policy. func (s *AssignmentPolicyService) UpdateAccountPolicy(ctx context.Context, id, accountID uint, req UpdatePolicyRequest) (*autoassignment.AssignmentPolicy, error) { if err := pkgvalidator.ValidateStruct(req); err != nil { return nil, err } policy, err := s.policyRepo.FindByID(ctx, id) if err != nil { return nil, fmt.Errorf("assignment policy not found: %w", err) } if policy.AccountID != accountID { return nil, fmt.Errorf("assignment policy does not belong to account") } if req.Policy != "" { policy.Policy = autoassignment.AssignmentPolicyType(req.Policy) } if req.FairDistributionLimit > 0 { policy.FairDistributionLimit = req.FairDistributionLimit } if req.FairDistributionWindow > 0 { policy.FairDistributionWindow = req.FairDistributionWindow } if req.Active != nil { policy.Active = *req.Active } if err := s.policyRepo.Update(ctx, policy); err != nil { applogger.L().Errorf("failed to update assignment policy: %v", err) return nil, fmt.Errorf("failed to update assignment policy: %w", err) } return policy, nil } // DeleteAccountPolicy deletes the account-level assignment policy. func (s *AssignmentPolicyService) DeleteAccountPolicy(ctx context.Context, id, accountID uint) error { policy, err := s.policyRepo.FindByID(ctx, id) if err != nil { return fmt.Errorf("assignment policy not found: %w", err) } if policy.AccountID != accountID { return fmt.Errorf("assignment policy does not belong to account") } if err := s.policyRepo.Delete(ctx, id); err != nil { return fmt.Errorf("failed to delete assignment policy: %w", err) } return nil } // GetInboxPolicy retrieves the inbox-level assignment policy override. func (s *AssignmentPolicyService) GetInboxPolicy(ctx context.Context, accountID, inboxID uint) (*autoassignment.InboxAssignmentPolicy, error) { policy, err := s.inboxPolicyRepo.FindByInbox(ctx, accountID, inboxID) if err != nil { return nil, fmt.Errorf("inbox assignment policy not found: %w", err) } return policy, nil } // CreateInboxPolicy creates an inbox-level assignment policy override. func (s *AssignmentPolicyService) CreateInboxPolicy(ctx context.Context, accountID uint, req CreateInboxPolicyRequest) (*autoassignment.InboxAssignmentPolicy, error) { if err := pkgvalidator.ValidateStruct(req); err != nil { return nil, err } active := true if req.Active != nil { active = *req.Active } limit := 5 if req.FairDistributionLimit > 0 { limit = req.FairDistributionLimit } window := 300 if req.FairDistributionWindow > 0 { window = req.FairDistributionWindow } policy := &autoassignment.InboxAssignmentPolicy{ AccountID: accountID, InboxID: req.InboxID, Policy: autoassignment.AssignmentPolicyType(req.Policy), FairDistributionLimit: limit, FairDistributionWindow: window, Active: active, } if err := s.inboxPolicyRepo.Create(ctx, policy); err != nil { applogger.L().Errorf("failed to create inbox assignment policy: %v", err) return nil, fmt.Errorf("failed to create inbox assignment policy: %w", err) } return policy, nil } // UpdateInboxPolicy updates an inbox-level assignment policy override. func (s *AssignmentPolicyService) UpdateInboxPolicy(ctx context.Context, id, accountID uint, req UpdateInboxPolicyRequest) (*autoassignment.InboxAssignmentPolicy, error) { if err := pkgvalidator.ValidateStruct(req); err != nil { return nil, err } policy, err := s.inboxPolicyRepo.FindByID(ctx, id) if err != nil { return nil, fmt.Errorf("inbox assignment policy not found: %w", err) } if policy.AccountID != accountID { return nil, fmt.Errorf("inbox assignment policy does not belong to account") } if req.Policy != "" { policy.Policy = autoassignment.AssignmentPolicyType(req.Policy) } if req.FairDistributionLimit > 0 { policy.FairDistributionLimit = req.FairDistributionLimit } if req.FairDistributionWindow > 0 { policy.FairDistributionWindow = req.FairDistributionWindow } if req.Active != nil { policy.Active = *req.Active } if err := s.inboxPolicyRepo.Update(ctx, policy); err != nil { applogger.L().Errorf("failed to update inbox assignment policy: %v", err) return nil, fmt.Errorf("failed to update inbox assignment policy: %w", err) } return policy, nil } // DeleteInboxPolicy deletes an inbox-level assignment policy override. func (s *AssignmentPolicyService) DeleteInboxPolicy(ctx context.Context, id, accountID uint) error { policy, err := s.inboxPolicyRepo.FindByID(ctx, id) if err != nil { return fmt.Errorf("inbox assignment policy not found: %w", err) } if policy.AccountID != accountID { return fmt.Errorf("inbox assignment policy does not belong to account") } if err := s.inboxPolicyRepo.Delete(ctx, id); err != nil { return fmt.Errorf("failed to delete inbox assignment policy: %w", err) } return nil } // AssignConversation assigns a specific conversation to an agent using auto-assignment. func (s *AssignmentPolicyService) AssignConversation(ctx context.Context, conversationID, inboxID, accountID uint) (uint, error) { agentID, err := s.assignSvc.AssignConversation(ctx, conversationID, inboxID, accountID) if err != nil { return 0, fmt.Errorf("failed to assign conversation: %w", err) } return agentID, nil }