# app/services/notificacao/gatilhos.rb # # Os pontos do sistema que DISPARAM um evento de notificação. Cada método monta # o contexto das {{variaveis}} daquele gatilho e entrega ao Despachante. # # Existe para que o código de negócio (consolidação, edição de lançamento, jobs) # tenha UMA linha de chamada, sem saber de template, canal ou destinatário — e # para que a lista de variáveis que o editor oferece (Notificacao::Variaveis) # tenha um par exato aqui. Variável anunciada na tela e nunca preenchida sai como # vazio na mensagem, o que é pior do que não existir. # # NADA aqui levanta: uma notificação não pode derrubar um fechamento nem uma # edição de lançamento. O Despachante já engole, e os métodos que montam o # contexto também. module Notificacao module Gatilhos module_function # Valor do motorista mudou DEPOIS do fechamento (lançamento editado, # desconto aplicado, pilar remarcado). Em rascunho o valor muda a cada # clique do wizard — avisar ali seria spam, não informação. def valor_alterado(consolidacao, consolidacao_motorista, anterior:) return unless consolidacao.finalizada? atual = consolidacao_motorista.valor_total.to_f return if anterior.to_f == atual Despachante.disparar_gatilho( gatilho: 'valor_alterado', dados: Variaveis.comuns_reais.merge( 'motorista' => consolidacao_motorista.motorista_nome, 'consolidacao' => consolidacao.nome, 'periodo' => periodo(consolidacao.data_inicio, consolidacao.data_fim), 'valor' => moeda(atual), 'entregas' => contar_entregas(consolidacao, consolidacao_motorista).to_s, 'link_painel' => link_painel, 'o_que_mudou' => "Valor passou de #{moeda(anterior)} para #{moeda(atual)}" ), assunto: "Valor alterado — #{consolidacao_motorista.motorista_nome}", corpo: "⚠️ O valor de #{consolidacao_motorista.motorista_nome} em #{consolidacao.nome} " \ "passou de #{moeda(anterior)} para #{moeda(atual)}.", # O próprio motorista é parte interessada: o valor DELE mudou depois de # fechado. Fica sob o `notificar_envolvido` do evento, então o ADM pode # desligar sem perder o aviso à diretoria. envolvido: motorista_usuario(consolidacao_motorista) ) rescue StandardError => e Rails.logger.error("[Gatilhos] valor_alterado: #{e.class}: #{e.message}") end # Dados da operação mudaram. Duas origens, os dois caminham para cá: # - edição de lançamento no SimpliRoute feita por dentro do sistema; # - varredura periódica que compara o espelho com o retrato anterior # (DetectarMudancasOperacaoJob) — é por onde chega a maioria, já que o # espelho sincroniza mudanças feitas fora daqui. def operacao_alterada(operacao:, o_que_mudou:, nf: nil) Despachante.disparar_gatilho( gatilho: 'operacao_alterada', dados: Variaveis.comuns_reais.merge( 'operacao' => operacao.to_s, 'o_que_mudou' => o_que_mudou.to_s, 'nf' => nf.to_s ), assunto: "Alteração na operação #{operacao}", corpo: "🔄 #{operacao}: #{o_que_mudou}#{nf.present? ? " (NF #{nf})" : ''}" ) rescue StandardError => e Rails.logger.error("[Gatilhos] operacao_alterada: #{e.class}: #{e.message}") end # Resumo periódico de um evento `agendado`. def resumo_agendado(evento, inicio:, fim:) numeros = Analytics::ResumoOperacao.new(inicio: inicio, fim: fim).numeros Despachante.disparar( chave: evento.chave, dados: Variaveis.comuns_reais.merge(numeros).merge( 'periodo' => periodo(inicio, fim) ), assunto: "Resumo da operação — #{periodo(inicio, fim)}", corpo: corpo_resumo(numeros, inicio, fim) ) rescue StandardError => e Rails.logger.error("[Gatilhos] resumo_agendado #{evento.chave}: #{e.class}: #{e.message}") end # ── Auxiliares ───────────────────────────────────────────── def corpo_resumo(numeros, inicio, fim) "📊 Resumo da operação — #{periodo(inicio, fim)}\n" \ "• Entregues: #{numeros['entregues']}\n" \ "• Não entregues: #{numeros['recusas']}\n" \ "• Em aberto: #{numeros['pendentes']}\n" \ "• Retentativas: #{numeros['retentativas']}\n" \ "• Notas fora da operação: #{numeros['fora_operacao']}" end def periodo(inicio, fim) "#{inicio&.strftime('%d/%m/%Y')} a #{fim&.strftime('%d/%m/%Y')}" end def link_painel "https://#{ENV.fetch('APP_HOST', 'localhost:3000')}/motorista" end # Mesma busca do NotificacaoService (casa pelo nome, que é o que a # consolidação guarda). Só roda em consolidação finalizada, então o custo # por recálculo é irrelevante. def motorista_usuario(consolidacao_motorista) User.motorista.ativos.find_by('LOWER(nome) = ?', consolidacao_motorista.motorista_nome.to_s.downcase) rescue StandardError nil end def contar_entregas(consolidacao, consolidacao_motorista) consolidacao.consolidacao_entregas .where(motorista_nome: consolidacao_motorista.motorista_nome) .where.not(tipo: :desconto) .count rescue StandardError nil end # Formatação vive em Notificacao::Formatador (era copiada aqui e no # NotificacaoService; com o catálogo de variáveis seriam três cópias). def moeda(valor) = Formatador.moeda(valor) end end