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

164 lines
5.9 KiB
Go

package service
import (
"context"
"encoding/json"
"fmt"
"github.com/gochat/gochat/internal/model"
"github.com/gochat/gochat/internal/repository"
applogger "github.com/gochat/gochat/pkg/logger"
)
// AgentCapacityPolicyService implements business logic for AgentCapacityPolicy operations.
// Reference: Chatwoot AgentCapacityPolicy + P2B M1 spec
type AgentCapacityPolicyService struct {
repo *repository.AgentCapacityPolicyRepo
}
// NewAgentCapacityPolicyService creates a new AgentCapacityPolicy service.
func NewAgentCapacityPolicyService(repo *repository.AgentCapacityPolicyRepo) *AgentCapacityPolicyService {
return &AgentCapacityPolicyService{repo: repo}
}
// CreateAgentCapacityPolicyRequest is the DTO for creating an agent capacity policy.
// Request body uses Chatwoot-style wrapper: { "agent_capacity_policy": { ... } }
type CreateAgentCapacityPolicyRequest struct {
Name string `json:"name" validate:"required"`
Description string `json:"description,omitempty"`
AssignmentLogic string `json:"assignment_logic" validate:"required"`
ExclusionRules json.RawMessage `json:"exclusion_rules,omitempty"`
}
// UpdateAgentCapacityPolicyRequest is the DTO for updating an agent capacity policy.
// Request body uses Chatwoot-style wrapper: { "agent_capacity_policy": { ... } }
type UpdateAgentCapacityPolicyRequest struct {
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
AssignmentLogic string `json:"assignment_logic,omitempty"`
ExclusionRules json.RawMessage `json:"exclusion_rules,omitempty"`
}
// validAssignmentLogic values allowed for AssignmentLogic field.
var validAssignmentLogic = map[string]bool{
"round_robin": true,
"least_busy": true,
}
// validateAssignmentLogic checks that the assignment logic is one of the allowed values.
func validateAssignmentLogic(logic string) error {
if !validAssignmentLogic[logic] {
return fmt.Errorf("invalid assignment_logic: must be one of round_robin, least_busy")
}
return nil
}
// paginate normalises page/pageSize to sane defaults and computes offset.
func paginate(page, pageSize int) (offset, normPageSize int) {
if page < 1 {
page = 1
}
if pageSize < 1 {
pageSize = 25
}
if pageSize > 100 {
pageSize = 100
}
return (page - 1) * pageSize, pageSize
}
// List retrieves all agent capacity policies for an account with pagination.
func (s *AgentCapacityPolicyService) List(ctx context.Context, accountID uint, page, pageSize int) ([]model.AgentCapacityPolicy, int64, error) {
offset, pageSize := paginate(page, pageSize)
policies, total, err := s.repo.FindByAccount(ctx, accountID, offset, pageSize)
if err != nil {
applogger.L().Errorf("List agent capacity policies for account %d: %v", accountID, err)
return nil, 0, fmt.Errorf("failed to list agent capacity policies: %w", err)
}
return policies, total, nil
}
// Create creates a new agent capacity policy within an account.
func (s *AgentCapacityPolicyService) Create(ctx context.Context, accountID uint, req CreateAgentCapacityPolicyRequest) (*model.AgentCapacityPolicy, error) {
if err := validateAssignmentLogic(req.AssignmentLogic); err != nil {
return nil, err
}
policy := &model.AgentCapacityPolicy{
AccountID: accountID,
Name: req.Name,
Description: req.Description,
AssignmentLogic: req.AssignmentLogic,
ExclusionRules: req.ExclusionRules,
}
if err := s.repo.Create(ctx, policy); err != nil {
applogger.L().Errorf("failed to create agent capacity policy: %v", err)
return nil, fmt.Errorf("failed to create agent capacity policy: %w", err)
}
return policy, nil
}
// Update updates an existing agent capacity policy scoped to an account.
func (s *AgentCapacityPolicyService) Update(ctx context.Context, id, accountID uint, req UpdateAgentCapacityPolicyRequest) (*model.AgentCapacityPolicy, error) {
policy, err := s.repo.FindByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("agent capacity policy not found: %w", err)
}
if policy.AccountID != accountID {
return nil, fmt.Errorf("agent capacity policy not found: policy does not belong to account %d", accountID)
}
// Merge fields: only update non-empty values
if req.Name != "" {
policy.Name = req.Name
}
if req.Description != "" {
policy.Description = req.Description
}
if req.AssignmentLogic != "" {
if err := validateAssignmentLogic(req.AssignmentLogic); err != nil {
return nil, err
}
policy.AssignmentLogic = req.AssignmentLogic
}
if req.ExclusionRules != nil {
policy.ExclusionRules = req.ExclusionRules
}
if err := s.repo.Update(ctx, policy); err != nil {
applogger.L().Errorf("failed to update agent capacity policy %d: %v", id, err)
return nil, fmt.Errorf("failed to update agent capacity policy: %w", err)
}
return policy, nil
}
// Delete deletes an agent capacity policy scoped to an account.
func (s *AgentCapacityPolicyService) Delete(ctx context.Context, id, accountID uint) error {
policy, err := s.repo.FindByID(ctx, id)
if err != nil {
return fmt.Errorf("agent capacity policy not found: %w", err)
}
if policy.AccountID != accountID {
return fmt.Errorf("agent capacity policy not found: policy does not belong to account %d", accountID)
}
if err := s.repo.Delete(ctx, id); err != nil {
applogger.L().Errorf("failed to delete agent capacity policy %d for account %d: %v", id, accountID, err)
return fmt.Errorf("failed to delete agent capacity policy: %w", err)
}
return nil
}
// GetByID retrieves a single agent capacity policy scoped to an account.
func (s *AgentCapacityPolicyService) GetByID(ctx context.Context, id, accountID uint) (*model.AgentCapacityPolicy, error) {
policy, err := s.repo.FindByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("agent capacity policy not found: %w", err)
}
if policy.AccountID != accountID {
return nil, fmt.Errorf("agent capacity policy not found: policy does not belong to account %d", accountID)
}
return policy, nil
}