Files
gochat/internal/service/assignment_policy_service.go
T
2026-06-04 15:44:48 +08:00

262 lines
9.4 KiB
Go

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
}