add crud for assignment and agent capacity

This commit is contained in:
Tanmay Deep Sharma
2025-07-30 12:08:03 +05:30
parent a96e75b904
commit a33ae58452
11 changed files with 916 additions and 0 deletions
@@ -0,0 +1,183 @@
# frozen_string_literal: true
class Api::V1::Accounts::AgentCapacityPoliciesController < Api::V1::Accounts::BaseController
before_action :ensure_enterprise_account
before_action :fetch_agent_capacity_policy, only: [:show, :update, :destroy, :set_inbox_limit, :remove_inbox_limit, :assign_user, :remove_user]
before_action :check_authorization
def index
@agent_capacity_policies = Current.account.agent_capacity_policies.includes(:users, :inbox_capacity_limits)
render json: { agent_capacity_policies: serialize_agent_capacity_policies(@agent_capacity_policies) }
end
def show
render json: { agent_capacity_policy: serialize_agent_capacity_policy(@agent_capacity_policy) }
end
def create
@agent_capacity_policy = Current.account.agent_capacity_policies.build(agent_capacity_policy_params)
if @agent_capacity_policy.save
render json: { agent_capacity_policy: serialize_agent_capacity_policy(@agent_capacity_policy) }, status: :created
else
render json: { errors: @agent_capacity_policy.errors.full_messages }, status: :unprocessable_entity
end
end
def update
if @agent_capacity_policy.update(agent_capacity_policy_params)
render json: { agent_capacity_policy: serialize_agent_capacity_policy(@agent_capacity_policy) }
else
render json: { errors: @agent_capacity_policy.errors.full_messages }, status: :unprocessable_entity
end
end
def destroy
if @agent_capacity_policy.destroy
head :ok
else
render json: { errors: @agent_capacity_policy.errors.full_messages }, status: :unprocessable_entity
end
end
# Set inbox capacity limit for a policy
def set_inbox_limit
inbox = Current.account.inboxes.find(params[:inbox_id])
inbox_limit = @agent_capacity_policy.inbox_capacity_limits.find_or_initialize_by(inbox: inbox)
if inbox_limit.update(conversation_limit: params[:conversation_limit])
render json: {
inbox_capacity_limit: serialize_inbox_capacity_limit(inbox_limit)
}
else
render json: { errors: inbox_limit.errors.full_messages }, status: :unprocessable_entity
end
end
# Remove inbox capacity limit from a policy
def remove_inbox_limit
inbox = Current.account.inboxes.find(params[:inbox_id])
inbox_limit = @agent_capacity_policy.inbox_capacity_limits.find_by(inbox: inbox)
if inbox_limit
if inbox_limit.destroy
head :ok
else
render json: { errors: inbox_limit.errors.full_messages }, status: :unprocessable_entity
end
else
render json: { error: 'Inbox limit not found' }, status: :not_found
end
end
# Assign a user to a capacity policy
def assign_user
user = Current.account.users.find(params[:user_id])
# Remove user from any existing capacity policy
Enterprise::AgentCapacityPolicyUser.where(user: user).destroy_all
# Assign to new policy
policy_user = @agent_capacity_policy.agent_capacity_policy_users.build(user: user)
if policy_user.save
render json: {
message: 'User assigned successfully',
agent_capacity_policy_user: serialize_policy_user(policy_user)
}
else
render json: { errors: policy_user.errors.full_messages }, status: :unprocessable_entity
end
end
# Remove a user from a capacity policy
def remove_user
user = Current.account.users.find(params[:user_id])
policy_user = @agent_capacity_policy.agent_capacity_policy_users.find_by(user: user)
if policy_user
if policy_user.destroy
head :ok
else
render json: { errors: policy_user.errors.full_messages }, status: :unprocessable_entity
end
else
render json: { error: 'User not assigned to this policy' }, status: :not_found
end
end
# Get current capacity status for an agent
def agent_capacity
user = Current.account.users.find(params[:agent_id])
inbox = params[:inbox_id] ? Current.account.inboxes.find(params[:inbox_id]) : nil
capacity_service = Enterprise::AssignmentV2::CapacityService.new
capacity_data = if inbox
capacity_service.get_agent_capacity(user, inbox)
else
capacity_service.get_agent_overall_capacity(user)
end
render json: { agent_capacity: capacity_data }
end
private
def ensure_enterprise_account
unless Current.account.feature_enabled?(:enterprise_agent_capacity)
render json: {
error: 'Agent capacity policies are only available for enterprise accounts'
}, status: :forbidden
end
end
def fetch_agent_capacity_policy
@agent_capacity_policy = Current.account.agent_capacity_policies.find(params[:id])
end
def agent_capacity_policy_params
params.require(:agent_capacity_policy).permit(:name, :description, exclusion_rules: {})
end
def check_authorization
authorize(Enterprise::AgentCapacityPolicy) if defined?(Enterprise::AgentCapacityPolicy)
end
def serialize_agent_capacity_policy(policy)
{
id: policy.id,
name: policy.name,
description: policy.description,
exclusion_rules: policy.exclusion_rules,
user_count: policy.users.count,
inbox_limit_count: policy.inbox_capacity_limits.count,
created_at: policy.created_at,
updated_at: policy.updated_at
}
end
def serialize_agent_capacity_policies(policies)
policies.map { |policy| serialize_agent_capacity_policy(policy) }
end
def serialize_inbox_capacity_limit(limit)
{
id: limit.id,
inbox_id: limit.inbox_id,
inbox_name: limit.inbox.name,
conversation_limit: limit.conversation_limit,
created_at: limit.created_at,
updated_at: limit.updated_at
}
end
def serialize_policy_user(policy_user)
{
id: policy_user.id,
user_id: policy_user.user_id,
user_name: policy_user.user.name,
user_email: policy_user.user.email,
created_at: policy_user.created_at
}
end
end
@@ -0,0 +1,222 @@
# frozen_string_literal: true
class Api::V1::Accounts::AssignmentMetricsController < Api::V1::Accounts::BaseController
before_action :check_authorization
before_action :validate_date_range
def index
@metrics = compute_assignment_metrics
render json: { assignment_metrics: @metrics }
end
def agent_history
@agent = Current.account.users.find(params[:agent_id])
@assignment_history = fetch_agent_assignment_history(@agent)
render json: {
agent: serialize_agent(@agent),
assignment_history: @assignment_history,
meta: pagination_meta
}
end
private
def compute_assignment_metrics
metrics = {
summary: compute_summary_metrics,
by_period: compute_period_metrics,
by_inbox: compute_inbox_metrics,
by_agent: compute_agent_metrics,
by_policy: compute_policy_metrics
}
metrics
end
def compute_summary_metrics
conversations = filter_conversations_by_date_range
{
total_assignments: conversations.count,
average_assignment_time: calculate_average_assignment_time(conversations),
assignments_per_agent: calculate_assignments_per_agent(conversations),
unassigned_conversations: Current.account.conversations.open.unassigned.count,
policies_active: Current.account.assignment_policies.enabled.count
}
end
def compute_period_metrics
group_by = params[:group_by] || 'day'
conversations = filter_conversations_by_date_range
case group_by
when 'hour'
group_by_hour(conversations)
when 'day'
group_by_day(conversations)
when 'week'
group_by_week(conversations)
when 'month'
group_by_month(conversations)
else
group_by_day(conversations)
end
end
def compute_inbox_metrics
inbox_id = params[:inbox_id]
base_query = filter_conversations_by_date_range
if inbox_id.present?
base_query = base_query.where(inbox_id: inbox_id)
end
base_query.joins(:inbox)
.group('inboxes.id', 'inboxes.name')
.count
.map { |k, v| { inbox_id: k[0], inbox_name: k[1], assignment_count: v } }
end
def compute_agent_metrics
agent_id = params[:agent_id]
base_query = filter_conversations_by_date_range.where.not(assignee_id: nil)
if agent_id.present?
base_query = base_query.where(assignee_id: agent_id)
end
base_query.joins(:assignee)
.group('users.id', 'users.name', 'users.email')
.count
.map { |k, v| { agent_id: k[0], agent_name: k[1], agent_email: k[2], assignment_count: v } }
.sort_by { |a| -a[:assignment_count] }
end
def compute_policy_metrics
# Get metrics grouped by assignment policy
policy_metrics = {}
Current.account.assignment_policies.includes(:inboxes).each do |policy|
inbox_ids = policy.inboxes.pluck(:id)
next if inbox_ids.empty?
conversations = filter_conversations_by_date_range.where(inbox_id: inbox_ids)
policy_metrics[policy.id] = {
policy_id: policy.id,
policy_name: policy.name,
assignment_order: policy.assignment_order,
total_assignments: conversations.count,
average_assignment_time: calculate_average_assignment_time(conversations),
inbox_count: inbox_ids.count
}
end
policy_metrics.values
end
def fetch_agent_assignment_history(agent)
conversations = agent.assigned_conversations
.includes(:inbox, :contact)
.where(created_at: date_range)
.order(created_at: :desc)
.page(params[:page])
.per(params[:per_page] || 50)
conversations.map do |conversation|
{
conversation_id: conversation.id,
inbox_id: conversation.inbox_id,
inbox_name: conversation.inbox.name,
contact_name: conversation.contact.name,
assigned_at: conversation.assignee_last_seen_at || conversation.created_at,
status: conversation.status,
created_at: conversation.created_at
}
end
end
def filter_conversations_by_date_range
Current.account.conversations.where(created_at: date_range)
end
def date_range
start_date = params[:start_date] ? Date.parse(params[:start_date]).beginning_of_day : 30.days.ago
end_date = params[:end_date] ? Date.parse(params[:end_date]).end_of_day : Time.current
start_date..end_date
end
def validate_date_range
if params[:start_date].present? && params[:end_date].present?
start_date = Date.parse(params[:start_date])
end_date = Date.parse(params[:end_date])
if start_date > end_date
render json: { error: 'Start date must be before end date' }, status: :bad_request
elsif (end_date - start_date).to_i > 365
render json: { error: 'Date range cannot exceed 365 days' }, status: :bad_request
end
end
rescue Date::Error
render json: { error: 'Invalid date format' }, status: :bad_request
end
def calculate_average_assignment_time(conversations)
assigned_conversations = conversations.where.not(assignee_id: nil)
return 0 if assigned_conversations.empty?
total_time = assigned_conversations.sum do |conv|
assignment_time = conv.assignee_last_seen_at || conv.updated_at
(assignment_time - conv.created_at).to_i
end
(total_time / assigned_conversations.count / 60).round(2) # Return in minutes
end
def calculate_assignments_per_agent(conversations)
assigned_count = conversations.where.not(assignee_id: nil).count
agent_count = conversations.where.not(assignee_id: nil).distinct.count(:assignee_id)
return 0 if agent_count.zero?
(assigned_count.to_f / agent_count).round(2)
end
def group_by_hour(conversations)
conversations.group_by_hour(:created_at).count
end
def group_by_day(conversations)
conversations.group_by_day(:created_at).count
end
def group_by_week(conversations)
conversations.group_by_week(:created_at).count
end
def group_by_month(conversations)
conversations.group_by_month(:created_at).count
end
def serialize_agent(agent)
{
id: agent.id,
name: agent.name,
email: agent.email,
avatar_url: agent.avatar_url
}
end
def pagination_meta
{
current_page: params[:page] || 1,
per_page: params[:per_page] || 50
}
end
def check_authorization
authorize(Conversation, :index?)
end
end
@@ -0,0 +1,78 @@
# frozen_string_literal: true
class Api::V1::Accounts::AssignmentPoliciesController < Api::V1::Accounts::BaseController
before_action :fetch_assignment_policy, only: [:show, :update, :destroy]
before_action :check_authorization
def index
@assignment_policies = Current.account.assignment_policies.includes(:inboxes)
render json: { assignment_policies: serialize_assignment_policies(@assignment_policies) }
end
def show
render json: { assignment_policy: serialize_assignment_policy(@assignment_policy) }
end
def create
@assignment_policy = Current.account.assignment_policies.build(assignment_policy_params)
if @assignment_policy.save
render json: { assignment_policy: serialize_assignment_policy(@assignment_policy) }, status: :created
else
render json: { errors: @assignment_policy.errors.full_messages }, status: :unprocessable_entity
end
end
def update
if @assignment_policy.update(assignment_policy_params)
render json: { assignment_policy: serialize_assignment_policy(@assignment_policy) }
else
render json: { errors: @assignment_policy.errors.full_messages }, status: :unprocessable_entity
end
end
def destroy
if @assignment_policy.destroy
head :ok
else
render json: { errors: @assignment_policy.errors.full_messages }, status: :unprocessable_entity
end
end
private
def fetch_assignment_policy
@assignment_policy = Current.account.assignment_policies.find(params[:id])
end
def assignment_policy_params
params.require(:assignment_policy).permit(
:name, :description, :assignment_order, :conversation_priority,
:fair_distribution_limit, :fair_distribution_window, :enabled
)
end
def serialize_assignment_policy(policy)
{
id: policy.id,
name: policy.name,
description: policy.description,
assignment_order: policy.assignment_order,
conversation_priority: policy.conversation_priority,
fair_distribution_limit: policy.fair_distribution_limit,
fair_distribution_window: policy.fair_distribution_window,
enabled: policy.enabled,
inbox_count: policy.inboxes.count,
created_at: policy.created_at,
updated_at: policy.updated_at
}
end
def serialize_assignment_policies(policies)
policies.map { |policy| serialize_assignment_policy(policy) }
end
def check_authorization
authorize(AssignmentPolicy)
end
end
@@ -0,0 +1,81 @@
# frozen_string_literal: true
class Api::V1::Accounts::InboxAssignmentPoliciesController < Api::V1::Accounts::BaseController
before_action :fetch_inbox
before_action :check_authorization
def show
@inbox_assignment_policy = @inbox.inbox_assignment_policy
if @inbox_assignment_policy
render json: {
inbox_assignment_policy: serialize_inbox_assignment_policy(@inbox_assignment_policy)
}
else
render json: {
inbox_assignment_policy: nil,
message: 'No assignment policy assigned to this inbox'
}
end
end
def create
# Remove existing assignment if any
@inbox.inbox_assignment_policy&.destroy
@assignment_policy = Current.account.assignment_policies.find(params[:assignment_policy_id])
@inbox_assignment_policy = @inbox.build_inbox_assignment_policy(assignment_policy: @assignment_policy)
if @inbox_assignment_policy.save
render json: {
inbox_assignment_policy: serialize_inbox_assignment_policy(@inbox_assignment_policy)
}, status: :created
else
render json: { errors: @inbox_assignment_policy.errors.full_messages }, status: :unprocessable_entity
end
end
def destroy
@inbox_assignment_policy = @inbox.inbox_assignment_policy
if @inbox_assignment_policy
if @inbox_assignment_policy.destroy
head :ok
else
render json: { errors: @inbox_assignment_policy.errors.full_messages }, status: :unprocessable_entity
end
else
render json: { error: 'No assignment policy found for this inbox' }, status: :not_found
end
end
private
def fetch_inbox
@inbox = Current.account.inboxes.find(params[:inbox_id])
end
def check_authorization
authorize(@inbox, :update?)
end
def serialize_inbox_assignment_policy(inbox_assignment_policy)
{
id: inbox_assignment_policy.id,
inbox_id: inbox_assignment_policy.inbox_id,
assignment_policy_id: inbox_assignment_policy.assignment_policy_id,
assignment_policy: {
id: inbox_assignment_policy.assignment_policy.id,
name: inbox_assignment_policy.assignment_policy.name,
description: inbox_assignment_policy.assignment_policy.description,
assignment_order: inbox_assignment_policy.assignment_policy.assignment_order,
conversation_priority: inbox_assignment_policy.assignment_policy.conversation_priority,
fair_distribution_limit: inbox_assignment_policy.assignment_policy.fair_distribution_limit,
fair_distribution_window: inbox_assignment_policy.assignment_policy.fair_distribution_window,
enabled: inbox_assignment_policy.assignment_policy.enabled
},
created_at: inbox_assignment_policy.created_at,
updated_at: inbox_assignment_policy.updated_at
}
end
end
+1
View File
@@ -102,6 +102,7 @@ class Account < ApplicationRecord
# Assignment V2 associations
has_many :assignment_policies, dependent: :destroy_async
has_many :agent_capacity_policies, dependent: :destroy_async, class_name: 'Enterprise::AgentCapacityPolicy' if ChatwootApp.enterprise?
has_one_attached :contacts_export
@@ -0,0 +1,37 @@
# frozen_string_literal: true
module AssignmentV2FeatureFlag
extend ActiveSupport::Concern
def assignment_v2_enabled?
# Check account-level feature flag
return false unless feature_enabled_for_account?
# Check for any inbox-level overrides
return false if inbox_level_override_disabled?
# Check system-wide killswitch
!GlobalConfig.get('assignment_v2_disabled', false)
end
private
def feature_enabled_for_account?
config = GlobalConfig.get('assignment_v2')
return false unless config&.dig('enabled')
# If no account allowlist, enable for all
allowed_accounts = config.dig('accounts')
return true if allowed_accounts.blank?
# Check if account is in allowlist
allowed_accounts.include?(self.id)
end
def inbox_level_override_disabled?
return false unless respond_to?(:id)
# Allow per-inbox disabling during migration
GlobalConfig.get("assignment_v2_disabled_inboxes", []).include?(self.id)
end
end
+23
View File
@@ -0,0 +1,23 @@
# frozen_string_literal: true
class AssignmentPolicyPolicy < ApplicationPolicy
def index?
@account_user.administrator?
end
def show?
@account_user.administrator?
end
def create?
@account_user.administrator?
end
def update?
@account_user.administrator?
end
def destroy?
@account_user.administrator?
end
end
+24
View File
@@ -224,6 +224,30 @@ Rails.application.routes.draw do
end
end
# Assignment V2 Routes
resources :assignment_policies
resources :inboxes, only: [] do
resource :assignment_policy, only: [:show, :create, :destroy], controller: 'inbox_assignment_policies'
end
# Agent Capacity Management (Enterprise)
resources :agent_capacity_policies do
member do
post 'inbox_limits/:inbox_id', to: 'agent_capacity_policies#set_inbox_limit'
delete 'inbox_limits/:inbox_id', to: 'agent_capacity_policies#remove_inbox_limit'
post 'users', to: 'agent_capacity_policies#assign_user'
delete 'users/:user_id', to: 'agent_capacity_policies#remove_user'
end
end
# Agent capacity status
get 'agents/:agent_id/capacity', to: 'agent_capacity_policies#agent_capacity'
# Assignment Metrics
get 'reports/assignment_metrics', to: 'assignment_metrics#index'
get 'agents/:agent_id/assignment_history', to: 'assignment_metrics#agent_history'
namespace :twitter do
resource :authorization, only: [:create]
end
@@ -0,0 +1,47 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: enterprise_agent_capacity_policy_users
#
# id :bigint not null, primary key
# agent_capacity_policy_id :bigint not null
# user_id :bigint not null
# created_at :datetime not null
#
# Indexes
#
# unique_user_capacity_policy (user_id) UNIQUE
# idx_capacity_policy_users_policy_id (agent_capacity_policy_id)
#
# Foreign Keys
#
# fk_rails_... (agent_capacity_policy_id => enterprise_agent_capacity_policies.id)
# fk_rails_... (user_id => users.id)
#
module Enterprise
class AgentCapacityPolicyUser < ApplicationRecord
self.table_name = 'enterprise_agent_capacity_policy_users'
# Associations
belongs_to :agent_capacity_policy, class_name: 'Enterprise::AgentCapacityPolicy'
belongs_to :user
# Validations
validates :user_id, uniqueness: true
# Delegations
delegate :account, to: :agent_capacity_policy
# Callbacks
after_create_commit :invalidate_user_cache
after_destroy_commit :invalidate_user_cache
private
def invalidate_user_cache
Rails.cache.delete_matched("assignment_v2:capacity:#{user_id}:*")
end
end
end
@@ -0,0 +1,45 @@
# frozen_string_literal: true
module Enterprise
class AgentCapacityPolicyPolicy < ApplicationPolicy
def index?
@account_user.administrator?
end
def show?
@account_user.administrator?
end
def create?
@account_user.administrator?
end
def update?
@account_user.administrator?
end
def destroy?
@account_user.administrator?
end
def set_inbox_limit?
@account_user.administrator?
end
def remove_inbox_limit?
@account_user.administrator?
end
def assign_user?
@account_user.administrator?
end
def remove_user?
@account_user.administrator?
end
def agent_capacity?
@account_user.administrator? || @account_user.agent?
end
end
end
@@ -0,0 +1,175 @@
# frozen_string_literal: true
module Enterprise
module AssignmentV2
class CapacityService
def initialize
@cache_ttl = 5.minutes
end
# Get agent's current capacity status for specific inbox
def get_agent_capacity(agent, inbox)
cache_key = capacity_cache_key(agent, inbox)
cached = Redis::Alfred.hgetall(cache_key)
return parse_cached_capacity(cached) if cached.present?
# Cache miss - compute from database
capacity = compute_agent_capacity(agent, inbox)
cache_capacity_data(cache_key, capacity)
capacity
end
# Get agent's overall capacity across all inboxes
def get_agent_overall_capacity(agent)
account = agent.accounts.first # Assuming we're working within account context
policy = get_agent_capacity_policy(agent, account)
return unlimited_capacity_summary unless policy
inboxes_data = []
total_current = 0
total_limit = 0
policy.inbox_capacity_limits.includes(:inbox).each do |inbox_limit|
inbox = inbox_limit.inbox
current = count_current_assignments(agent, inbox, policy)
limit = inbox_limit.conversation_limit
total_current += current
total_limit += limit
inboxes_data << {
inbox_id: inbox.id,
inbox_name: inbox.name,
current_assignments: current,
conversation_limit: limit,
available_capacity: [limit - current, 0].max
}
end
{
policy_id: policy.id,
policy_name: policy.name,
total_current_assignments: total_current,
total_conversation_limit: total_limit,
total_available_capacity: [total_limit - total_current, 0].max,
exclusion_rules: policy.exclusion_rules,
inboxes: inboxes_data
}
end
private
def compute_agent_capacity(agent, inbox)
account = inbox.account
policy = get_agent_capacity_policy(agent, account)
return unlimited_capacity if policy.nil?
inbox_limit = policy.inbox_capacity_limits.find_by(inbox: inbox)
return unlimited_capacity if inbox_limit.nil?
current_assignments = count_current_assignments(agent, inbox, policy)
{
total_capacity: inbox_limit.conversation_limit,
current_assignments: current_assignments,
available_capacity: [inbox_limit.conversation_limit - current_assignments, 0].max,
policy_id: policy.id,
policy_name: policy.name,
exclusion_rules: policy.exclusion_rules
}
end
def get_agent_capacity_policy(agent, account)
account_user = account.account_users.find_by(user: agent)
return nil unless account_user&.agent_capacity_policy_id
Enterprise::AgentCapacityPolicy.find_by(id: account_user.agent_capacity_policy_id)
end
def count_current_assignments(agent, inbox, policy)
scope = agent.assigned_conversations
.where(inbox: inbox, status: 'open')
# Apply exclusion rules from policy
scope = apply_exclusion_rules(scope, policy)
scope.count
end
def apply_exclusion_rules(scope, policy)
rules = policy.exclusion_rules || {}
# Exclude conversations with specific labels
if rules['labels'].present?
scope = scope.where.not(id:
ConversationLabel.joins(:label)
.where(labels: { title: rules['labels'] })
.select(:conversation_id)
)
end
# Exclude conversations older than X hours
if rules['hours_threshold'].present?
cutoff = rules['hours_threshold'].hours.ago
scope = scope.where('conversations.created_at > ?', cutoff)
end
scope
end
def unlimited_capacity
{
total_capacity: Float::INFINITY,
current_assignments: 0,
available_capacity: Float::INFINITY,
policy_id: nil,
policy_name: 'No capacity policy',
exclusion_rules: {}
}
end
def unlimited_capacity_summary
{
policy_id: nil,
policy_name: 'No capacity policy',
total_current_assignments: 0,
total_conversation_limit: Float::INFINITY,
total_available_capacity: Float::INFINITY,
exclusion_rules: {},
inboxes: []
}
end
def parse_cached_capacity(cached)
{
total_capacity: cached['total_capacity'].to_i,
current_assignments: cached['current_assignments'].to_i,
available_capacity: cached['available_capacity'].to_i,
policy_id: cached['policy_id'].presence&.to_i,
policy_name: cached['policy_name'] || 'No capacity policy',
exclusion_rules: JSON.parse(cached['exclusion_rules'] || '{}')
}
end
def cache_capacity_data(cache_key, capacity)
Redis::Alfred.multi do |multi|
multi.hset(cache_key,
'total_capacity', capacity[:total_capacity],
'current_assignments', capacity[:current_assignments],
'available_capacity', capacity[:available_capacity],
'policy_id', capacity[:policy_id],
'policy_name', capacity[:policy_name],
'exclusion_rules', capacity[:exclusion_rules].to_json
)
multi.expire(cache_key, @cache_ttl)
end
end
def capacity_cache_key(agent, inbox)
"assignment_v2:capacity:#{agent.id}:#{inbox.id}"
end
end
end
end