Passei um tempo tentando achar uma solução porem não achei
Criei utilizando IA, uma serviço que direciona automaticamente um ticket.
Esse código é responsável por atribuir automaticamente tickets (chamados) a agentes de suporte com base na disponibilidade de departamentos e na ordem de atendimento. Ele realiza as seguintes operações principais:
Leitura da Configuração do Banco:
A função read_db_config() abre e extrai os parâmetros de conexão (usuário, senha, host, banco de dados) a partir do arquivo de configuração PHP (ost-config.php). Caso alguma configuração esteja faltando, gera um erro.
Identificação de Departamentos Ativos e Emails:
get_active_departments(config): Consulta a tabela ost_active_departments para obter os departamentos que estão ativos.
get_emails_and_departments(config): Recupera da tabela ost_email um mapeamento entre emails e os IDs dos departamentos associados.
Seleção do Próximo Agente Disponível:
A função get_next_agent(department_id, config) busca na tabela ost_staff (em conjunto com as tabelas ost_team_member e ost_team) o agente que:
Pertence ao departamento informado;
Está ativo, não está de férias, e não é administrador;
Não é o líder da equipe;
Foi o menos recentemente designado (ordena por last_assigned).
Atribuição do Ticket:
assign_ticket(ticket_id, agent_id, config): Atualiza o ticket na tabela ost_ticket para definir o agente responsável e atualiza o campo last_assigned do agente na tabela ost_staff para o momento atual.
Processamento dos Tickets:
A função process_tickets(department_id, config):
Consulta a tabela ost_ticket para tickets não atribuídos (onde staff_id é 0) para um departamento específico;
Para cada ticket encontrado, obtém o próximo agente disponível e o atribui por meio de assign_ticket.
Loop de Execução:
O código entra num loop infinito (com pausa de 60 segundos) em que:
Lê os departamentos ativos e os emails associados;
Para cada email cujo departamento esteja ativo, processa os tickets não atribuídos para aquele departamento.
Log de Informações e Erros:
As funções log_info, log_error e log_message registram mensagens com timestamp num arquivo (auto_assign.log) e também imprimem as mensagens no console.
Funcionamento na Prática
Configuração: O código se conecta ao banco de dados utilizando as credenciais extraídas do arquivo de configuração.
Verificação Periódica: A cada 60 segundos, ele verifica quais departamentos estão ativos e, para cada email associado a um departamento ativo, busca por tickets sem agente atribuído.
Distribuição de Chamados: Para cada ticket encontrado, ele escolhe o agente mais adequado (baseado na ordem de last_assigned) e atualiza o ticket e o registro do agente para refletir a nova atribuição.
Registro de Logs: Cada ação ou eventual erro é registrado para facilitar a auditoria e a solução de problemas.
Esse código, portanto, implementa uma lógica simples de fila, garantindo que os tickets sejam distribuídos entre os agentes conforme a ordem de atendimento, sem repetir o agente até que todos tenham sido contemplados (por meio do campo last_assigned na tabela ost_staff). Se precisar de mais alguma coisa ou tiver dúvidas, estou à disposição!
` import mysql.connector
from mysql.connector import Error
import time
import re
import datetime
def read_db_config():
config = {}
try:
with open('/var/www/html/upload/include/ost-config.php', 'r') as file:
content = file.read()
user_match = re.search(r"define\('DBUSER',\s*'([^']+)'\);", content)
password_match = re.search(r"define\('DBPASS',\s*'([^']+)'\);", content)
host_match = re.search(r"define\('DBHOST',\s*'([^']+)'\);", content)
database_match = re.search(r"define\('DBNAME',\s*'([^']+)'\);", content)
if user_match and password_match and host_match and database_match:
config['user'] = user_match.group(1)
config['password'] = password_match.group(1)
config['host'] = host_match.group(1)
config['database'] = database_match.group(1)
else:
missing = []
if not user_match:
missing.append("DBUSER")
if not password_match:
missing.append("DBPASS")
if not host_match:
missing.append("DBHOST")
if not database_match:
missing.append("DBNAME")
raise ValueError(f"Faltam configuracoes: {', '.join(missing)}")
except Exception as e:
log_error(f"Erro ao ler o arquivo de configuracao: {e}")
return config
def get_active_departments(config):
try:
connection = mysql.connector.connect(**config)
cursor = connection.cursor(dictionary=True)
query = "SELECT dept_id FROM ost_active_departments"
cursor.execute(query)
results = cursor.fetchall()
active_departments = [result['dept_id'] for result in results]
return active_departments
except Error as e:
log_error(f"Erro ao obter departamentos ativos: {e}")
return []
finally:
if 'connection' in locals() and connection.is_connected():
cursor.close()
connection.close()
def get_emails_and_departments(config):
try:
connection = mysql.connector.connect(**config)
cursor = connection.cursor(dictionary=True)
query = "SELECT email, dept_id FROM ost_email"
cursor.execute(query)
results = cursor.fetchall()
email_dept_map = {result['email']: result['dept_id'] for result in results}
return email_dept_map
except Error as e:
log_error(f"Erro ao obter emails e departamentos: {e}")
return {}
finally:
if 'connection' in locals() and connection.is_connected():
cursor.close()
connection.close()
def get_next_agent(department_id, config):
try:
connection = mysql.connector.connect(**config)
cursor = connection.cursor(dictionary=True)
query = """
SELECT staff.staff_id
FROM ost_staff staff
JOIN ost_team_member member ON staff.staff_id = member.staff_id
JOIN ost_team team ON member.team_id = team.team_id
WHERE staff.dept_id = %s
AND staff.isadmin = 0
AND staff.isactive = 1
AND staff.onvacation = 0
AND staff.staff_id != team.lead_id
ORDER BY staff.last_assigned ASC
LIMIT 1
"""
cursor.execute(query, (department_id,))
result = cursor.fetchone()
if result:
log_info(f"Proximo agente encontrado para o departamento {department_id}: {result['staff_id']}")
return result['staff_id']
else:
log_info(f"Nenhum agente encontrado para o departamento {department_id}")
return None
except Error as e:
log_error(f"Erro ao obter proximo agente: {e}")
finally:
if 'connection' in locals() and connection.is_connected():
cursor.close()
connection.close()
def assign_ticket(ticket_id, agent_id, config):
try:
connection = mysql.connector.connect(**config)
cursor = connection.cursor()
update_ticket_query = "UPDATE ost_ticket SET staff_id = %s WHERE ticket_id = %s"
cursor.execute(update_ticket_query, (agent_id, ticket_id))
update_agent_query = "UPDATE ost_staff SET last_assigned = NOW() WHERE staff_id = %s"
cursor.execute(update_agent_query, (agent_id,))
connection.commit()
log_info(f"Ticket {ticket_id} atribuido ao agente {agent_id}")
except Error as e:
log_error(f"Erro ao atribuir ticket: {e}")
finally:
if 'connection' in locals() and connection.is_connected():
cursor.close()
connection.close()
def process_tickets(department_id, config):
try:
connection = mysql.connector.connect(**config)
cursor = connection.cursor(dictionary=True)
query = "SELECT ticket_id FROM ost_ticket WHERE staff_id = 0 AND dept_id = %s"
cursor.execute(query, (department_id,))
tickets = cursor.fetchall()
if tickets:
log_info(f"Tickets nao atribuidos encontrados para o departamento {department_id}: {len(tickets)}")
for ticket in tickets:
agent_id = get_next_agent(department_id, config)
if agent_id:
assign_ticket(ticket['ticket_id'], agent_id, config)
else:
log_info(f"Nenhum ticket nao atribuido encontrado para o departamento {department_id}")
except Error as e:
log_error(f"Erro ao processar tickets: {e}")
finally:
if 'connection' in locals() and connection.is_connected():
cursor.close()
connection.close()
def log_info(message):
log_message("INFO", message)
def log_error(message):
log_message("ERROR", message)
def log_message(level, message):
timestamp = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
log_entry = f"{timestamp} [{level}] {message}"
log_file = '/var/www/html/upload/script/auto_assign.log'
with open(log_file, 'a') as file:
file.write(log_entry + '\n')
print(log_entry) # Para exibir no console também
Loop principal para processar todos os emails e seus respectivos departamentos
config = read_db_config()
if not config:
log_error("Erro ao ler as configuracoes do banco de dados. Verifique o arquivo ost-config.php.")
exit()
while True:
active_departments = get_active_departments(config)
if not active_departments:
log_info("Nenhum departamento ativo encontrado. Aguardando...")
time.sleep(60)
continue
email_dept_map = get_emails_and_departments(config)
for email, dept_id in email_dept_map.items():
if dept_id in active_departments:
log_info(f"Iniciando processamento para o email {email} e departamento {dept_id}")
process_tickets(dept_id, config)
time.sleep(60) # Verificar novos tickets a cada 60 segundos
`
Pagina
`<?php
require('admin.inc.php');
require_once INCLUDE_DIR.'class.csrf.php';
global $ost, $cfg;
// Function to fetch departments from the database
function fetch_departments() {
$departments = [];
$query = "SELECT id, name FROM " . DEPT_TABLE;
if ($res = db_query($query)) {
while ($row = db_fetch_array($res)) {
$departments[$row['id']] = $row['name'];
}
}
return $departments;
}
// Function to fetch active departments
function fetch_active_departments() {
$active_departments = [];
$query = "SELECT dept_id FROM ost_active_departments";
if ($res = db_query($query)) {
while ($row = db_fetch_array($res)) {
$active_departments[] = $row['dept_id'];
}
}
return $active_departments;
}
$saved = false; // Variable to check if the form was saved
// Handle form submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// Check CSRF token
if (!$ost->checkCSRFToken()) {
Http::response(400, __('Valid CSRF Token Required'));
exit;
}
// Get selected active and inactive departments
$selected_active_departments = isset($_POST['active_departments']) ? $_POST['active_departments'] : [];
$selected_inactive_departments = isset($_POST['inactive_departments']) ? $_POST['inactive_departments'] : [];
// Fetch current active departments
$current_active_departments = fetch_active_departments();
// Calculate which departments to add or remove
$to_add = array_diff($selected_active_departments, $current_active_departments);
$to_remove = array_intersect($selected_inactive_departments, $current_active_departments);
// Add new active departments to the database
foreach ($to_add as $dept_id) {
db_query("INSERT INTO ost_active_departments (dept_id) VALUES (" . db_input($dept_id) . ")");
}
// Remove departments no longer active
foreach ($to_remove as $dept_id) {
db_query("DELETE FROM ost_active_departments WHERE dept_id = " . db_input($dept_id));
}
$saved = true; // Set the variable to true when saved
}
$departments = fetch_departments();
$active_departments = fetch_active_departments();
include(STAFFINC_DIR.'header.inc.php');
?>
<h2>Configurações de Atribuição Automática de Tickets</h2>
<form method="post" action="auto_assign.php">
<?php csrf_token(); ?>
<div style="display: flex;">
<div style="margin-right: 20px;">
<label for="inactive_departments">Departamentos Inativos:</label>
<select id="inactive_departments" name="inactive_departments[]" multiple style="width: 200px; height: 300px;">
<?php
foreach ($departments as $dept_id => $dept_name) {
if (!in_array($dept_id, $active_departments)) {
echo "<option value=\"$dept_id\">$dept_name</option>";
}
}
?>
</select>
</div>
<div style="display: flex; flex-direction: column; justify-content: center;">
<button type="button" onclick="moveItems('inactive_departments', 'active_departments')">>>></button>
<button type="button" onclick="moveItems('active_departments', 'inactive_departments')"><<<</button>
</div>
<div>
<label for="active_departments">Departamentos Ativos:</label>
<select id="active_departments" name="active_departments[]" multiple style="width: 200px; height: 300px;">
<?php
foreach ($departments as $dept_id => $dept_name) {
if (in_array($dept_id, $active_departments)) {
echo "<option value=\"$dept_id\">$dept_name</option>";
}
}
?>
</select>
</div>
</div>
<br>
<input type="submit" value="Salvar">
<?php
if ($saved) {
echo '<div id="notification" class="msg success">Configurações salvas com sucesso!</div>';
}
?>
</form>
<script>
function moveItems(sourceId, destinationId) {
var source = document.getElementById(sourceId);
var destination = document.getElementById(destinationId);
var selectedOptions = [...source.selectedOptions];
selectedOptions.forEach(option => {
destination.appendChild(option);
});
}
// Notification timeout
document.addEventListener('DOMContentLoaded', function() {
const notification = document.getElementById('notification');
if (notification) {
setTimeout(() => {
notification.style.display = 'none';
}, 3000); // Hide after 3 seconds
}
});
</script>
<style>
.msg.success {
color: green;
font-weight: bold;
margin-top: 10px;
}
</style>
<?php
include(STAFFINC_DIR.'footer.inc.php');
?>
Menu
if ($nav) {
$nav->addSubMenu(array(
'desc' => __('Auto Assign Settings'),
'href' => 'auto_assign.php',
'iconclass' => 'fa-cogs'
));
}
`