120 lines
4.9 KiB
Ruby
120 lines
4.9 KiB
Ruby
# app/models/evento_notificacao.rb
|
|
#
|
|
# Um evento que dispara mensagem. O ADM cadastra quantos quiser e liga cada um a
|
|
# grupos de contato — mas o GATILHO sai de GATILHOS, porque gatilho é código.
|
|
#
|
|
# manual → botão "disparar agora" (aviso pontual)
|
|
# consolidacao_finalizada → NotificacaoService.notificar_finalizacao
|
|
# pagamento_efetuado → NotificacaoService.notificar_pagamento
|
|
# valor_alterado → edição de lançamento / desconto após o fechamento
|
|
# operacao_alterada → NF que entrou/saiu, entrega que mudou de status
|
|
# agendado → resumo periódico (frequencia + hora)
|
|
#
|
|
# `sistema: true` marca os que nascem com o app: editáveis e desligáveis, mas
|
|
# não apagáveis — apagar deixaria o código disparando no vazio.
|
|
class EventoNotificacao < ApplicationRecord
|
|
self.table_name = 'eventos_notificacao'
|
|
|
|
GATILHOS = %w[manual consolidacao_finalizada pagamento_efetuado
|
|
valor_alterado operacao_alterada agendado].freeze
|
|
|
|
GATILHO_LABEL = {
|
|
'manual' => 'Disparo manual (botão)',
|
|
'consolidacao_finalizada' => 'Consolidação finalizada',
|
|
'pagamento_efetuado' => 'Pagamento efetuado',
|
|
'valor_alterado' => 'Valor alterado no fechamento',
|
|
'operacao_alterada' => 'Dados da operação mudaram',
|
|
'agendado' => 'Agendado (resumo periódico)'
|
|
}.freeze
|
|
|
|
FREQUENCIAS = %w[diaria semanal].freeze
|
|
|
|
# Gatilhos que se referem a UMA pessoa específica (o motorista do pagamento),
|
|
# e não só a grupos. Só neles `notificar_envolvido` faz sentido.
|
|
GATILHOS_COM_ENVOLVIDO = %w[consolidacao_finalizada pagamento_efetuado valor_alterado].freeze
|
|
|
|
has_many :assinaturas, class_name: 'GrupoEventoAssinatura', dependent: :destroy
|
|
has_many :grupos_contato, through: :assinaturas, source: :grupo_contato
|
|
has_many :notificacao_envios, dependent: :nullify
|
|
has_many :mensagem_templates, dependent: :destroy
|
|
|
|
accepts_nested_attributes_for :assinaturas, allow_destroy: true
|
|
|
|
validates :nome, presence: true
|
|
validates :chave, presence: true, uniqueness: { case_sensitive: false }
|
|
validates :gatilho, inclusion: { in: GATILHOS }
|
|
validates :frequencia, inclusion: { in: FREQUENCIAS }, if: :agendado?
|
|
validates :hora, numericality: { only_integer: true, greater_than_or_equal_to: 0,
|
|
less_than_or_equal_to: 23 }, allow_nil: true
|
|
validates :dia_semana, numericality: { only_integer: true, greater_than_or_equal_to: 0,
|
|
less_than_or_equal_to: 6 }, allow_nil: true
|
|
validate :semanal_tem_dia
|
|
|
|
before_validation :normalizar
|
|
|
|
scope :ativos, -> { where(ativo: true) }
|
|
scope :ordenados, -> { order(Arel.sql('LOWER(nome)')) }
|
|
scope :do_gatilho, ->(g) { ativos.where(gatilho: g) }
|
|
|
|
def agendado? = gatilho == 'agendado'
|
|
def manual? = gatilho == 'manual'
|
|
def envolvido_aplicavel? = GATILHOS_COM_ENVOLVIDO.include?(gatilho)
|
|
def gatilho_label = GATILHO_LABEL[gatilho] || gatilho
|
|
|
|
# Só pode apagar o que o ADM criou.
|
|
def apagavel? = !sistema?
|
|
|
|
# Template do canal, criando um vazio em memória quando ainda não existe —
|
|
# assim a tela do editor abre igual para evento novo e evento já configurado.
|
|
def template(canal)
|
|
mensagem_templates.find { |t| t.canal == canal.to_s } ||
|
|
mensagem_templates.build(canal: canal.to_s, blocos: [])
|
|
end
|
|
|
|
# O corpo configurado no editor vence o texto padrão do código. Um template
|
|
# ativo porém VAZIO não conta (mandaria mensagem em branco).
|
|
def template_utilizavel(canal)
|
|
t = mensagem_templates.detect { |x| x.canal == canal.to_s }
|
|
t&.utilizavel? ? t : nil
|
|
end
|
|
|
|
def variaveis = Notificacao::Variaveis.para(gatilho)
|
|
|
|
# Contatos que devem receber este evento, sem repetição — a mesma pessoa pode
|
|
# estar em dois grupos assinantes.
|
|
def destinatarios(canal)
|
|
ids = assinaturas.where(ativo: true)
|
|
.select { |a| a.cobre_canal?(canal) }
|
|
.map(&:grupo_contato_id)
|
|
return Contato.none if ids.empty?
|
|
|
|
Contato.ativos.where(grupo_contato_id: ids).com_canal(canal).distinct
|
|
end
|
|
|
|
# Já passou da hora de disparar hoje/esta semana?
|
|
def vencido?(agora = Time.current)
|
|
return false unless agendado? && ativo?
|
|
return false unless agora.hour >= hora.to_i
|
|
return false if frequencia == 'semanal' && agora.wday != dia_semana.to_i
|
|
return true if ultimo_disparo_em.blank?
|
|
|
|
ultimo_disparo_em < agora.beginning_of_day
|
|
end
|
|
|
|
private
|
|
|
|
def normalizar
|
|
self.nome = nome.to_s.strip.squeeze(' ').presence
|
|
self.chave = chave.presence || nome.to_s.parameterize(separator: '_').presence
|
|
self.chave = chave.to_s.strip.downcase.presence
|
|
self.dia_semana = nil unless agendado? && frequencia == 'semanal'
|
|
self.frequencia = nil unless agendado?
|
|
end
|
|
|
|
def semanal_tem_dia
|
|
return unless agendado? && frequencia == 'semanal' && dia_semana.blank?
|
|
|
|
errors.add(:dia_semana, 'é obrigatório na frequência semanal')
|
|
end
|
|
end
|