Files
Reem-Notas/app/models/contato.rb

115 lines
4.2 KiB
Ruby

# app/models/contato.rb
#
# Destinatário das notificações, cadastrado à mão pelo ADM: nome, número e/ou
# e-mail, e o grupo a que pertence.
#
# Telefone é gravado SEMPRE em E.164 (+5511920051157). O ADM digita como quiser
# — "(11) 92005-1157", "011 92005 1157" — e a normalização acontece aqui, uma
# vez, na gravação. Mandar o texto cru para o provedor devolve erro de destino
# inválido que o rescue do envio esconderia.
class Contato < ApplicationRecord
belongs_to :grupo_contato, optional: true
belongs_to :user, optional: true
has_many :notificacao_envios, dependent: :nullify
EMAIL_RE = /\A[^@\s]+@[^@\s]+\z/
# Um destinatário é uma PESSOA (telefone e/ou e-mail) ou um GRUPO do WhatsApp.
# O grupo entra aqui, e não em tabela própria, para herdar grupo interno,
# assinatura de eventos e log de envios sem duplicar essa máquina.
TIPOS = %w[pessoa grupo_whatsapp].freeze
# Identificador de grupo do WhatsApp: "120363012345678901@g.us".
JID_GRUPO_RE = /\A\d{5,}@g\.us\z/
validates :nome, presence: true
validates :tipo, inclusion: { in: TIPOS }
validates :email, format: { with: EMAIL_RE }, allow_blank: true
validate :telefone_ou_email, if: :pessoa?
validate :telefone_valido, if: :pessoa?
validate :grupo_identificado, if: :grupo_whatsapp?
before_validation :normalizar
scope :ativos, -> { where(ativo: true) }
scope :ordenados, -> { order(Arel.sql('LOWER(nome)')) }
scope :pessoas, -> { where(tipo: 'pessoa') }
scope :grupos_whatsapp, -> { where(tipo: 'grupo_whatsapp') }
# "Tem WhatsApp" = número de pessoa OU identificador de grupo.
scope :com_whatsapp, -> {
where.not(telefone: [nil, '']).or(where.not(whatsapp_grupo_jid: [nil, '']))
}
scope :com_email, -> { where.not(email: [nil, '']) }
# Filtra pelo canal do disparo. 'ambos' = quem tiver qualquer um dos dois.
scope :com_canal, ->(canal) {
case canal.to_s
when 'whatsapp' then com_whatsapp
when 'email' then com_email
else where("(telefone IS NOT NULL AND telefone <> '') OR (email IS NOT NULL AND email <> '')")
end
}
def pessoa? = tipo.to_s != 'grupo_whatsapp'
def grupo_whatsapp? = tipo.to_s == 'grupo_whatsapp'
def whatsapp? = destino_whatsapp.present?
def email? = email.present? && pessoa?
# Para onde o WhatsApp vai: o JID do grupo ou o número da pessoa. É o que a
# ponte recebe — ela aceita as duas formas.
def destino_whatsapp
grupo_whatsapp? ? whatsapp_grupo_jid.presence : telefone.presence
end
# {{contato}} na mensagem. Em grupo não existe "primeiro nome" — cortar
# "Motoristas SP" em "Motoristas" só empobreceria o texto.
def primeiro_nome
grupo_whatsapp? ? nome.to_s : nome.to_s.split.first.to_s
end
private
def normalizar
self.nome = nome.to_s.strip.squeeze(' ').presence
self.tipo = tipo.presence || 'pessoa'
if grupo_whatsapp?
# Grupo não tem telefone nem e-mail: limpar evita um cadastro meio pessoa,
# meio grupo, que mandaria a mesma mensagem duas vezes.
self.telefone = nil
self.email = nil
self.whatsapp_grupo_jid = whatsapp_grupo_jid.to_s.strip.presence
self.whatsapp_grupo_nome = whatsapp_grupo_nome.to_s.strip.presence
else
self.whatsapp_grupo_jid = nil
self.whatsapp_grupo_nome = nil
self.email = email.to_s.strip.downcase.presence
# Mantém o que foi digitado se não der para normalizar — a validação acusa
# e o ADM vê o próprio valor de volta no campo para corrigir.
bruto = telefone.to_s.strip.presence
self.telefone = bruto && (ConfiguracaoNotificacao.normalizar_telefone(bruto) || bruto)
end
end
def grupo_identificado
return if whatsapp_grupo_jid.to_s.match?(JID_GRUPO_RE)
errors.add(:whatsapp_grupo_jid,
'inválido: escolha um grupo da lista (o identificador termina em @g.us)')
end
def telefone_ou_email
return if telefone.present? || email.present?
errors.add(:base, 'Informe ao menos um WhatsApp ou um e-mail.')
end
def telefone_valido
return if telefone.blank?
return if ConfiguracaoNotificacao.normalizar_telefone(telefone).present?
errors.add(:telefone, 'não parece um número brasileiro válido (DDD + número)')
end
end