191 lines
7.4 KiB
Ruby
191 lines
7.4 KiB
Ruby
# app/services/notificacao_service.rb
|
|
#
|
|
# Envia notificações aos motoristas quando uma consolidação é finalizada ou
|
|
# quando o pagamento é efetuado.
|
|
#
|
|
# Canais e credenciais vêm de ConfiguracaoNotificacao (tela /admin/
|
|
# configuracao_notificacao), com fallback para o .env.
|
|
#
|
|
# Falhas de envio NUNCA quebram o fluxo de finalização — apenas logam. Isso é
|
|
# intencional: um SMTP fora do ar não pode impedir o fechamento de um pagamento.
|
|
# Para DIAGNOSTICAR o envio, use os botões de teste da tela, que usam
|
|
# Notificacao::TesteEmail / Notificacao::TesteWhatsapp e mostram o erro real.
|
|
class NotificacaoService
|
|
EVENTOS = {
|
|
fechado: { mailer: :pagamento_fechado,
|
|
chave: 'consolidacao_finalizada',
|
|
emoji: '🚚',
|
|
titulo: 'Seu pagamento de entregas foi fechado.',
|
|
cta: 'Acesse seu painel para baixar o extrato:' },
|
|
pago: { mailer: :pagamento_efetuado,
|
|
chave: 'pagamento_efetuado',
|
|
emoji: '✅',
|
|
titulo: 'Seu pagamento foi efetuado.',
|
|
cta: 'Acesse seu painel para conferir:' }
|
|
}.freeze
|
|
|
|
def self.notificar_finalizacao(consolidacao)
|
|
new(consolidacao).notificar_todos
|
|
end
|
|
|
|
# Avisa UM motorista de que o pagamento dele foi efetuado.
|
|
def self.notificar_pagamento(consolidacao, consolidacao_motorista)
|
|
new(consolidacao).enviar_aviso_pagamento(consolidacao_motorista)
|
|
end
|
|
|
|
def initialize(consolidacao)
|
|
@consolidacao = consolidacao
|
|
@config = ConfiguracaoNotificacao.instancia
|
|
end
|
|
|
|
def notificar_todos
|
|
@consolidacao.consolidacao_motoristas.ativos.each { |cm| notificar(cm, :fechado) }
|
|
end
|
|
|
|
def enviar_aviso_pagamento(consolidacao_motorista)
|
|
notificar(consolidacao_motorista, :pago)
|
|
end
|
|
|
|
private
|
|
|
|
def notificar(cm, evento)
|
|
user = motorista_de(cm)
|
|
|
|
# Aviso pessoal ao motorista: continua com o e-mail formatado do
|
|
# ConsolidacaoMailer (não vira texto puro) e passa a usar o provedor de
|
|
# WhatsApp escolhido na tela — Baileys por QR ou Twilio.
|
|
if user
|
|
enviar_whatsapp(user, cm, evento) if @config.whatsapp_habilitado?
|
|
enviar_email(user, cm, evento) if @config.email_habilitado? && user.email.present?
|
|
end
|
|
|
|
# Cópia para os grupos que assinam este evento (diretoria, operação...).
|
|
# `envolvido: nil` de propósito: o motorista já foi avisado acima e receberia
|
|
# a mensagem duas vezes.
|
|
avisar_grupos(cm, evento, user)
|
|
end
|
|
|
|
# O corpo aqui é o mesmo texto do WhatsApp — na etapa do editor de blocos ele
|
|
# passa a vir do template configurado pelo ADM.
|
|
def avisar_grupos(cm, evento, user)
|
|
evt = EVENTOS.fetch(evento)
|
|
Notificacao::Despachante.disparar(
|
|
chave: evt[:chave],
|
|
dados: variaveis(cm, user),
|
|
assunto: "#{evt[:titulo]} — #{@consolidacao.nome}",
|
|
corpo: mensagem_para_grupos(cm, evento, user)
|
|
)
|
|
end
|
|
|
|
# Contexto das {{variaveis}} do editor de blocos. As chaves são as mesmas de
|
|
# Notificacao::Variaveis::FECHAMENTO — é o que o ADM vê na paleta da tela.
|
|
def variaveis(cm, user)
|
|
Notificacao::Variaveis.comuns_reais.merge(
|
|
'motorista' => user&.nome.presence || cm.motorista_nome,
|
|
'consolidacao' => @consolidacao.nome,
|
|
'periodo' => "#{l_data(@consolidacao.data_inicio)} a #{l_data(@consolidacao.data_fim)}",
|
|
'valor' => moeda(cm.valor_total),
|
|
# ConsolidacaoMotorista não guarda contagem — só valor_total. As entregas
|
|
# do motorista são as linhas classificadas na consolidação (mesma fonte do
|
|
# #recalcular_valor), descontando os lançamentos de desconto, que não são
|
|
# entregas.
|
|
'entregas' => entregas_do_motorista(cm).to_s,
|
|
'link_painel' => "https://#{ENV.fetch('APP_HOST', 'localhost:3000')}/motorista"
|
|
)
|
|
end
|
|
|
|
def l_data(data)
|
|
data&.strftime('%d/%m/%Y').to_s
|
|
end
|
|
|
|
def entregas_do_motorista(cm)
|
|
@consolidacao.consolidacao_entregas
|
|
.where(motorista_nome: cm.motorista_nome)
|
|
.where.not(tipo: :desconto)
|
|
.count
|
|
rescue StandardError => e
|
|
Rails.logger.warn("[Notificacao] contagem de entregas indisponível: #{e.class}: #{e.message}")
|
|
nil
|
|
end
|
|
|
|
def mensagem_para_grupos(cm, evento, user)
|
|
evt = EVENTOS.fetch(evento)
|
|
"#{evt[:emoji]} #{evt[:titulo]}\n" \
|
|
"👤 #{user&.nome || cm.motorista_nome}\n" \
|
|
"📋 #{@consolidacao.nome}\n" \
|
|
"💰 Valor: #{moeda(cm.valor_total)}"
|
|
end
|
|
|
|
def motorista_de(cm)
|
|
User.motorista.ativos.find_by('LOWER(nome) = ?', cm.motorista_nome.downcase)
|
|
end
|
|
|
|
def enviar_whatsapp(user, cm, evento)
|
|
# O telefone no cadastro pode estar como "(11) 92005-1157"; a normalização
|
|
# para E.164 acontece dentro de Notificacao::Whatsapp — mandar o texto cru
|
|
# devolve erro de destino inválido que o rescue esconderia.
|
|
destino = ConfiguracaoNotificacao.normalizar_telefone(user.telefone)
|
|
return if destino.blank?
|
|
|
|
texto = mensagem(user, cm, evento)
|
|
envio = registrar_envio(evento, canal: 'whatsapp', destino: destino, user: user, corpo: texto)
|
|
|
|
resposta = Notificacao::Whatsapp.enviar(para: destino, texto: texto, config: @config)
|
|
if resposta.ok?
|
|
envio&.marcar_enviado!
|
|
Rails.logger.info("[Notificacao] WhatsApp (#{evento}) enviado para #{user.nome}")
|
|
else
|
|
envio&.marcar_falha!(resposta.erro)
|
|
Rails.logger.error("[Notificacao] Falha WhatsApp (#{evento}) #{user.nome}: #{resposta.erro}")
|
|
end
|
|
end
|
|
|
|
def enviar_email(user, cm, evento)
|
|
evt = EVENTOS.fetch(evento)
|
|
envio = registrar_envio(evento, canal: 'email', destino: user.email, user: user,
|
|
corpo: mensagem(user, cm, evento), assunto: evt[:titulo])
|
|
|
|
ConsolidacaoMailer.public_send(evt[:mailer], user, @consolidacao, cm).deliver_later
|
|
# `deliver_later` só ENFILEIRA: marcar como enviado aqui significa "saiu do
|
|
# nosso lado". Uma falha do SMTP aparece no log do Rails, não aqui.
|
|
envio&.marcar_enviado!
|
|
Rails.logger.info("[Notificacao] Email (#{evento}) agendado para #{user.email}")
|
|
rescue StandardError => e
|
|
envio&.marcar_falha!("#{e.class}: #{e.message}")
|
|
Rails.logger.error("[Notificacao] Falha email (#{evento}) #{user.email}: #{e.class}: #{e.message}")
|
|
end
|
|
|
|
# Log do aviso pessoal. Nunca levanta: se a tabela ainda não existir (deploy
|
|
# antes do migrate), o envio precisa seguir mesmo sem registro.
|
|
def registrar_envio(evento, canal:, destino:, user:, corpo:, assunto: nil)
|
|
NotificacaoEnvio.create!(
|
|
evento_notificacao: evento_registrado(evento), user: user, canal: canal,
|
|
destino: destino, assunto: assunto, corpo: corpo, status: 'pendente'
|
|
)
|
|
rescue StandardError => e
|
|
Rails.logger.warn("[Notificacao] sem log de envio (#{e.class}: #{e.message})")
|
|
nil
|
|
end
|
|
|
|
def evento_registrado(evento)
|
|
@eventos_cache ||= {}
|
|
@eventos_cache[evento] ||= EventoNotificacao.find_by(chave: EVENTOS.fetch(evento)[:chave])
|
|
end
|
|
|
|
def mensagem(user, cm, evento)
|
|
evt = EVENTOS.fetch(evento)
|
|
"Olá #{user.nome.split.first}! #{evt[:emoji]}\n" \
|
|
"#{evt[:titulo]}\n" \
|
|
"📋 #{@consolidacao.nome}\n" \
|
|
"💰 Valor: #{moeda(cm.valor_total)}\n" \
|
|
"#{evt[:cta]} https://#{ENV.fetch('APP_HOST', 'localhost:3000')}/motorista"
|
|
end
|
|
|
|
# Moeda BR com separador de milhar: R$ 1.234,56
|
|
def moeda(valor)
|
|
inteiro, decimais = format('%.2f', valor.to_f).split('.')
|
|
inteiro = inteiro.reverse.gsub(/(\d{3})(?=\d)/, '\1.').reverse
|
|
"R$ #{inteiro},#{decimais}"
|
|
end
|
|
end
|