102 lines
3.5 KiB
Ruby
102 lines
3.5 KiB
Ruby
# app/services/romaneios/enriquecimento_gade.rb
|
|
#
|
|
# Preenche APARELHO, TELEFONES e NOME do romaneio a partir da tabela da operação
|
|
# (gade_entregas_*). É o que dispensa a SEGUNDA PLANILHA do programa Python: lá o
|
|
# operador subia um .xlsx só com NOTA FISCAL + STATUS para marcar quem é NOVO; aqui
|
|
# o STATUS já está no banco, na própria operação.
|
|
#
|
|
# A tabela é SOMENTE LEITURA (apenas SELECT), o nome passa por Operacao.valida? +
|
|
# quote_table_name e as NFs por conn.quote.
|
|
module Romaneios
|
|
class EnriquecimentoGade
|
|
# chave usada no SELECT => coluna real na gade.
|
|
#
|
|
# ⚠️ `status` PRECISA sair aliasado: o espelho de rastreio também tem uma coluna
|
|
# `status` (o estado da visita), e qualquer query que junte as duas trocaria uma
|
|
# pela outra em silêncio. Analytics::OperacaoMetricas já faz esse mesmo alias
|
|
# (GADE_OPCIONAIS) pelo mesmo motivo.
|
|
COLUNAS = {
|
|
'nota_fiscal' => 'nota_fiscal',
|
|
'nome_completo' => 'nome_completo',
|
|
'telefones' => 'telefones',
|
|
'status' => 'status_gade'
|
|
}.freeze
|
|
|
|
# Sem essas, não há o que enriquecer.
|
|
ESSENCIAIS = %w[nota_fiscal].freeze
|
|
|
|
attr_reader :tabela
|
|
|
|
def initialize(tabela)
|
|
@tabela = tabela.to_s
|
|
raise ArgumentError, "Operação inválida: #{@tabela}" unless Operacao.valida?(@tabela)
|
|
end
|
|
|
|
# { '90507' => { aparelho: 'SIM', telefones: '(11) ...', nome: 'CICERA ...' } }
|
|
# NF que não estiver na operação simplesmente não vem no Hash — é assim que a
|
|
# tela marca "fora da operação" (plano avulso / de inclusão), mesma semântica de
|
|
# Analytics::NotasForaOperacao.
|
|
def para_notas(notas)
|
|
lista = Array(notas).map { |n| n.to_s.strip }.reject(&:empty?).uniq
|
|
return {} if lista.empty?
|
|
|
|
registros(lista).each_with_object({}) do |r, memo|
|
|
nf = r['nota_fiscal'].to_s.strip
|
|
next if nf.empty?
|
|
|
|
memo[nf] = {
|
|
aparelho: Normalizador.aparelho_por_status(r['status_gade']),
|
|
telefones: r['telefones'].to_s.strip,
|
|
nome: Normalizador.nome(r['nome_completo'])
|
|
}
|
|
end
|
|
end
|
|
|
|
# Colunas que esta operação NÃO tem — a tela precisa DIZER isso.
|
|
#
|
|
# O esquema das gade_entregas_* varia por operação (só `nota_fiscal` é
|
|
# garantida). Quando falta `status`, o APARELHO sai vazio para TODO MUNDO — e um
|
|
# romaneio em que ninguém recebe aparelho, entregue calado, é pior do que um
|
|
# aviso feio na tela.
|
|
def colunas_ausentes
|
|
@colunas_ausentes ||= COLUNAS.keys - existentes
|
|
end
|
|
|
|
def utilizavel?
|
|
(ESSENCIAIS - existentes).empty?
|
|
end
|
|
|
|
def label
|
|
Operacao.label(@tabela)
|
|
end
|
|
|
|
private
|
|
|
|
def conn
|
|
ActiveRecord::Base.connection
|
|
end
|
|
|
|
def existentes
|
|
@existentes ||= conn.columns(@tabela).map(&:name)
|
|
end
|
|
|
|
def registros(lista)
|
|
return [] unless utilizavel?
|
|
|
|
valores = lista.map { |n| conn.quote(n) }.join(', ')
|
|
sql = "SELECT #{selects} FROM #{conn.quote_table_name(@tabela)} g " \
|
|
"WHERE g.nota_fiscal IN (#{valores})"
|
|
conn.select_all(sql).to_a
|
|
end
|
|
|
|
# Coluna que não existir vira NULL com o mesmo alias, preservando a forma do
|
|
# resultado — mesmo padrão de SimpliRoute::PlanilhaCarga#selects. As chaves de
|
|
# COLUNAS são whitelist fixa (não há entrada de usuário no SQL).
|
|
def selects
|
|
COLUNAS.map do |coluna, apelido|
|
|
existentes.include?(coluna) ? "g.#{coluna} AS #{apelido}" : "CAST(NULL AS text) AS #{apelido}"
|
|
end.join(', ')
|
|
end
|
|
end
|
|
end
|