Implantação da geração de mensagem de forma livre e mudança na engine de mensagem
This commit is contained in:
155
app/services/notificacao/despachante.rb
Normal file
155
app/services/notificacao/despachante.rb
Normal file
@@ -0,0 +1,155 @@
|
||||
# app/services/notificacao/despachante.rb
|
||||
#
|
||||
# Entrega UMA mensagem já montada aos destinatários de um evento.
|
||||
#
|
||||
# Separação proposital: o Despachante resolve QUEM recebe, por qual canal,
|
||||
# respeita o intervalo entre envios e registra o resultado. O TEXTO vem do
|
||||
# template de blocos do evento (MensagemTemplate), renderizado por canal — HTML
|
||||
# no e-mail, texto puro no WhatsApp, do mesmo template.
|
||||
#
|
||||
# `corpo:`/`assunto:` são o FALLBACK: valem quando o evento ainda não tem
|
||||
# template montado (o texto padrão do código) ou quando a mensagem é digitada na
|
||||
# hora (disparo manual). Sem esse fallback, ligar o editor apagaria as
|
||||
# notificações dos eventos que já funcionavam.
|
||||
#
|
||||
# Destinatários = contatos dos grupos que assinam o evento
|
||||
# + (opcional) a pessoa diretamente envolvida no fato, que é o
|
||||
# comportamento que já existia: o motorista dono do pagamento.
|
||||
#
|
||||
# ⚠️ INTERVALO: no WhatsApp por sessão QR (canal não oficial), disparar em
|
||||
# rajada para dezenas de números é a forma mais rápida de perder o número.
|
||||
# Por isso o envio roda em job, com pausa configurável entre mensagens —
|
||||
# nunca dentro da requisição web.
|
||||
module Notificacao
|
||||
class Despachante
|
||||
# Dispara em background. Nunca levanta: uma notificação não pode derrubar o
|
||||
# fechamento de um pagamento (mesma regra do NotificacaoService).
|
||||
def self.disparar(chave:, dados: {}, assunto: nil, corpo: nil, envolvido: nil)
|
||||
evento = EventoNotificacao.ativos.find_by(chave: chave.to_s)
|
||||
return if evento.nil?
|
||||
|
||||
NotificacaoJob.perform_later(evento.id, (dados || {}).stringify_keys,
|
||||
assunto.to_s, corpo.to_s, envolvido&.id)
|
||||
rescue StandardError => e
|
||||
Rails.logger.error("[Despachante] falha ao agendar #{chave}: #{e.class}: #{e.message}")
|
||||
end
|
||||
|
||||
def initialize(evento, dados: {}, assunto: nil, corpo: nil, envolvido: nil)
|
||||
@evento = evento
|
||||
@dados = (dados || {}).stringify_keys
|
||||
@assunto = assunto.presence || evento.nome
|
||||
@corpo = corpo.to_s
|
||||
@envolvido = envolvido
|
||||
@config = ConfiguracaoNotificacao.instancia
|
||||
end
|
||||
|
||||
def executar
|
||||
enviar_whatsapp if @config.whatsapp_habilitado?
|
||||
enviar_email if @config.email_habilitado?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def enviar_whatsapp
|
||||
intervalo = @config.intervalo_envio
|
||||
|
||||
destinos_whatsapp.each_with_index do |(destino, contato, user), i|
|
||||
# Pausa ENTRE mensagens, não antes da primeira.
|
||||
sleep(intervalo) if i.positive? && intervalo.positive?
|
||||
|
||||
corpo = corpo_whatsapp(contato, user)
|
||||
envio = registrar(canal: 'whatsapp', destino: destino, contato: contato,
|
||||
user: user, corpo: corpo)
|
||||
resposta = Whatsapp.enviar(para: destino, texto: corpo, config: @config)
|
||||
resposta.ok? ? envio.marcar_enviado! : envio.marcar_falha!(resposta.erro)
|
||||
end
|
||||
end
|
||||
|
||||
# ── Corpo por canal ─────────────────────────────────────────
|
||||
# A renderização acontece POR DESTINATÁRIO, não uma vez só: {{contato}} é o
|
||||
# primeiro nome de quem recebe, então um render compartilhado mandaria o
|
||||
# nome da primeira pessoa para todo mundo. Renderizar é manipulação de
|
||||
# string — o custo por destinatário é irrelevante perto do envio em si.
|
||||
def dados_para(contato, user)
|
||||
nome = contato&.primeiro_nome.presence || user&.nome.to_s.split.first
|
||||
@dados.merge('contato' => nome.to_s)
|
||||
end
|
||||
|
||||
# O template do editor vence; sem template utilizável, cai no texto que o
|
||||
# chamador passou (texto padrão do código ou mensagem digitada na hora).
|
||||
def corpo_whatsapp(contato, user)
|
||||
template = @evento.template_utilizavel('whatsapp')
|
||||
return @corpo unless template
|
||||
|
||||
template.renderizador(dados_para(contato, user)).texto.presence || @corpo
|
||||
end
|
||||
|
||||
# [assunto, corpo, html?] — com template o e-mail sai em HTML montado; sem
|
||||
# template, texto puro que o mailer formata.
|
||||
def email_montado(contato, user)
|
||||
template = @evento.template_utilizavel('email')
|
||||
return [@assunto, @corpo, false] unless template
|
||||
|
||||
r = template.renderizador(dados_para(contato, user))
|
||||
[r.interpolar(template.assunto).presence || @assunto, r.html, true]
|
||||
end
|
||||
|
||||
def enviar_email
|
||||
destinos_email.each do |destino, contato, user|
|
||||
assunto, corpo, html = email_montado(contato, user)
|
||||
envio = registrar(canal: 'email', destino: destino, contato: contato,
|
||||
user: user, corpo: corpo, assunto: assunto)
|
||||
begin
|
||||
NotificacaoMailer.mensagem(destino, assunto, corpo, html: html).deliver_now
|
||||
envio.marcar_enviado!
|
||||
rescue StandardError => e
|
||||
envio.marcar_falha!("#{e.class}: #{e.message}")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# [[destino, contato, user], ...] sem repetir o mesmo número/e-mail — a mesma
|
||||
# pessoa pode ser contato de um grupo E o envolvido no fato.
|
||||
def destinos_whatsapp
|
||||
lista = @evento.destinatarios('whatsapp').map { |c| [c.telefone, c, c.user] }
|
||||
lista += envolvido_whatsapp
|
||||
dedup(lista)
|
||||
end
|
||||
|
||||
def destinos_email
|
||||
lista = @evento.destinatarios('email').map { |c| [c.email, c, c.user] }
|
||||
lista += envolvido_email
|
||||
dedup(lista)
|
||||
end
|
||||
|
||||
def envolvido_whatsapp
|
||||
return [] unless notificar_envolvido?
|
||||
|
||||
numero = ConfiguracaoNotificacao.normalizar_telefone(@envolvido.telefone)
|
||||
numero.present? ? [[numero, nil, @envolvido]] : []
|
||||
end
|
||||
|
||||
def envolvido_email
|
||||
return [] unless notificar_envolvido? && @envolvido.email.present?
|
||||
|
||||
[[@envolvido.email, nil, @envolvido]]
|
||||
end
|
||||
|
||||
def notificar_envolvido?
|
||||
@envolvido.present? && @evento.notificar_envolvido?
|
||||
end
|
||||
|
||||
def dedup(lista)
|
||||
lista.reject { |destino, _, _| destino.blank? }
|
||||
.uniq { |destino, _, _| destino.to_s.downcase }
|
||||
end
|
||||
|
||||
def registrar(canal:, destino:, contato:, user:, corpo:, assunto: nil)
|
||||
NotificacaoEnvio.create!(
|
||||
evento_notificacao: @evento, contato: contato, user: user,
|
||||
canal: canal, destino: destino, assunto: assunto,
|
||||
corpo: corpo, status: 'pendente'
|
||||
)
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user