Implantação da geração de mensagem de forma livre e mudança na engine de mensagem
This commit is contained in:
64
app/jobs/detectar_mudancas_operacao_job.rb
Normal file
64
app/jobs/detectar_mudancas_operacao_job.rb
Normal file
@@ -0,0 +1,64 @@
|
||||
# app/jobs/detectar_mudancas_operacao_job.rb
|
||||
#
|
||||
# Compara os números de cada operação com o retrato anterior (OperacaoSnapshot)
|
||||
# e dispara `operacao_alterada` no que mudou.
|
||||
#
|
||||
# POR QUE VARREDURA e não gatilho no código: a maior parte das mudanças da
|
||||
# operação NÃO passa por aqui — ela acontece no SimpliRoute e chega pelo sync do
|
||||
# espelho, que é read-only do nosso lado. Sem comparar retratos, "o que mudou"
|
||||
# seria só o que o próprio sistema editou, que é a minoria dos casos.
|
||||
#
|
||||
# A PRIMEIRA execução de cada operação só grava o retrato e não avisa nada: sem
|
||||
# retrato anterior, toda operação existente pareceria "nova" e o primeiro deploy
|
||||
# dispararia uma mensagem por operação cadastrada.
|
||||
class DetectarMudancasOperacaoJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform
|
||||
return unless EventoNotificacao.ativos.exists?(gatilho: 'operacao_alterada')
|
||||
|
||||
Operacao.nomes_validos.each { |tabela| verificar(tabela) }
|
||||
rescue StandardError => e
|
||||
Rails.logger.error("[MudancasOperacao] #{e.class}: #{e.message}")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def verificar(tabela)
|
||||
atuais = contar(tabela)
|
||||
snapshot = OperacaoSnapshot.para(tabela)
|
||||
|
||||
if snapshot.new_record?
|
||||
snapshot.registrar!(atuais)
|
||||
return
|
||||
end
|
||||
|
||||
diferenca = snapshot.diferenca(atuais)
|
||||
return if diferenca.empty?
|
||||
|
||||
Notificacao::Gatilhos.operacao_alterada(
|
||||
operacao: Operacao.label(tabela),
|
||||
o_que_mudou: OperacaoSnapshot.descrever(diferenca)
|
||||
)
|
||||
snapshot.registrar!(atuais)
|
||||
rescue StandardError => e
|
||||
# Uma operação problemática não pode impedir a varredura das demais.
|
||||
Rails.logger.error("[MudancasOperacao] #{tabela}: #{e.class}: #{e.message}")
|
||||
end
|
||||
|
||||
# Sem filtro de data: a comparação é do estado ATUAL da operação inteira, que
|
||||
# é o que muda quando uma NF entra, sai ou troca de status.
|
||||
#
|
||||
# ⚠️ CUSTO: OperacaoMetricas carrega as visitas da operação em memória para
|
||||
# deduplicar por NF. Uma operação mensal tem alguns milhares de linhas, e o
|
||||
# job varre TODAS as tabelas a cada execução (8×/dia pelo schedule). É o
|
||||
# preço de reusar a mesma contagem do dashboard em vez de escrever um SQL
|
||||
# paralelo que divergiria dele. Se passar a incomodar, o caminho é restringir
|
||||
# a varredura às operações do mês corrente e do anterior — as antigas não
|
||||
# mudam mais.
|
||||
def contar(tabela)
|
||||
m = Analytics::OperacaoMetricas.new(tabelas: [tabela])
|
||||
{ total_notas: m.total, entregues: m.sucesso,
|
||||
nao_entregues: m.recusas, pendentes: m.pendentes }
|
||||
end
|
||||
end
|
||||
48
app/jobs/disparar_eventos_agendados_job.rb
Normal file
48
app/jobs/disparar_eventos_agendados_job.rb
Normal file
@@ -0,0 +1,48 @@
|
||||
# app/jobs/disparar_eventos_agendados_job.rb
|
||||
#
|
||||
# Varre os eventos de gatilho `agendado` e dispara os que venceram. Roda de hora
|
||||
# em hora pelo cron do container (config/schedule.rb).
|
||||
#
|
||||
# POR QUE DE HORA EM HORA e não "no horário exato": o evento guarda hora e
|
||||
# frequência escolhidas na tela, e essas podem mudar a qualquer momento — reler
|
||||
# o crontab a cada edição seria acoplar a UI ao cron do sistema. Uma varredura
|
||||
# horária resolve com uma linha fixa no schedule, e `EventoNotificacao#vencido?`
|
||||
# garante que cada evento dispara UMA vez por dia (ele grava ultimo_disparo_em).
|
||||
#
|
||||
# PERÍODO do resumo — recortes FECHADOS, para o número não mudar depois de
|
||||
# enviado:
|
||||
# diária → o dia anterior
|
||||
# semanal → os 7 dias anteriores
|
||||
class DispararEventosAgendadosJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(agora = Time.current)
|
||||
EventoNotificacao.ativos.where(gatilho: 'agendado').find_each do |evento|
|
||||
next unless evento.vencido?(agora)
|
||||
|
||||
inicio, fim = janela(evento, agora)
|
||||
resumo = Analytics::ResumoOperacao.new(inicio: inicio, fim: fim)
|
||||
|
||||
# Resumo zerado (feriado, fim de semana) só treinaria o leitor a ignorar a
|
||||
# mensagem. Marca como disparado assim mesmo, senão tentaria de hora em
|
||||
# hora até o dia virar.
|
||||
if resumo.vazio?
|
||||
evento.update_columns(ultimo_disparo_em: agora)
|
||||
Rails.logger.info("[Agendados] #{evento.chave}: período sem movimento, nada enviado")
|
||||
next
|
||||
end
|
||||
|
||||
Notificacao::Gatilhos.resumo_agendado(evento, inicio: inicio, fim: fim)
|
||||
evento.update_columns(ultimo_disparo_em: agora)
|
||||
end
|
||||
rescue StandardError => e
|
||||
Rails.logger.error("[Agendados] #{e.class}: #{e.message}")
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def janela(evento, agora)
|
||||
hoje = agora.to_date
|
||||
evento.frequencia == 'semanal' ? [hoje - 7, hoje - 1] : [hoje - 1, hoje - 1]
|
||||
end
|
||||
end
|
||||
26
app/jobs/notificacao_job.rb
Normal file
26
app/jobs/notificacao_job.rb
Normal file
@@ -0,0 +1,26 @@
|
||||
# app/jobs/notificacao_job.rb
|
||||
#
|
||||
# Entrega das notificações fora da requisição web.
|
||||
#
|
||||
# POR QUE JOB: o envio de WhatsApp pausa entre uma mensagem e outra (o canal por
|
||||
# QR bane número que dispara em rajada). Um `sleep` de 5s vezes 30 contatos
|
||||
# dentro do controller pendura o worker Puma por dois minutos e meio.
|
||||
#
|
||||
# ⚠️ O adapter é o :async do Rails (fila em memória, igual ao `deliver_later` que
|
||||
# o app já usa): job pendente se perde se o container reiniciar. Para
|
||||
# notificação isso é aceitável — o log em notificacao_envios mostra o que
|
||||
# ficou 'pendente' e a tela permite reenviar.
|
||||
class NotificacaoJob < ApplicationJob
|
||||
queue_as :default
|
||||
|
||||
def perform(evento_id, dados = {}, assunto = nil, corpo = nil, envolvido_id = nil)
|
||||
evento = EventoNotificacao.find_by(id: evento_id)
|
||||
return if evento.nil? || !evento.ativo?
|
||||
|
||||
envolvido = envolvido_id && User.find_by(id: envolvido_id)
|
||||
Notificacao::Despachante.new(evento, dados: dados, assunto: assunto,
|
||||
corpo: corpo, envolvido: envolvido).executar
|
||||
rescue StandardError => e
|
||||
Rails.logger.error("[NotificacaoJob] evento #{evento_id}: #{e.class}: #{e.message}")
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user