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

131 lines
5.3 KiB
Ruby

# app/models/operacao.rb
#
# Operações são as tabelas EXTERNAS que o cliente carrega no banco, uma por
# operação/mês (ex.: "gade_entregas_ubs_sul_leste_jun_2026"). Cada tabela tem a
# coluna `nota_fiscal`, que casa com db_reem_simplerout_2026.reference_id::text.
#
# Estas tabelas são SOMENTE LEITURA — apenas SELECT, nunca migration/INSERT/etc.
#
# Como o nome da tabela entra em SQL (FROM dinâmico), TODA entrada externa passa
# por uma whitelist (#nomes_validos) antes de ser usada — proteção anti-injection.
class Operacao
PREFIXO = ENV.fetch('OPERACOES_TABLE_PREFIX', 'gade_entregas_').freeze
class << self
# [{ tabela: 'gade_entregas_...', label: 'Ubs Sul Leste Jun 2026' }, ...]
def todas
nomes_validos.map { |t| { tabela: t, label: label(t) } }
end
# Nomes reais de tabelas no schema public que batem com o prefixo.
# Sem cache de processo: o cliente sobe novas tabelas de operação a qualquer
# momento e elas precisam aparecer sem reiniciar o servidor (a consulta ao
# catálogo é barata).
def nomes_validos
conn.select_values(
"SELECT table_name FROM information_schema.tables " \
"WHERE table_schema = 'public' AND table_name LIKE #{conn.quote("#{PREFIXO}%")} ORDER BY table_name"
)
end
# Interseção da lista recebida com as tabelas que existem de fato.
def sanitizar(lista)
Array(lista).map(&:to_s).uniq & nomes_validos
end
def valida?(nome)
nomes_validos.include?(nome.to_s)
end
# Rótulo amigável: tira o prefixo e formata em CAIXA ALTA (para não parecer o
# nome cru da tabela do banco).
def label(tabela)
tabela.to_s.delete_prefix(PREFIXO).tr('_', ' ').strip.upcase
end
# Meses usados nos nomes das tabelas (gade_entregas_emad_fev_2026). Aceita a
# abreviação (fev) E o nome por extenso (fevereiro) — o cliente usa os dois
# (ex.: "..._ubs_norte_maio_2026"), então ambos precisam cair no mês certo.
MESES = {
'jan' => [1, 'Janeiro'], 'janeiro' => [1, 'Janeiro'],
'fev' => [2, 'Fevereiro'], 'fevereiro' => [2, 'Fevereiro'],
'mar' => [3, 'Março'], 'marco' => [3, 'Março'], 'março' => [3, 'Março'],
'abr' => [4, 'Abril'], 'abril' => [4, 'Abril'],
'mai' => [5, 'Maio'], 'maio' => [5, 'Maio'],
'jun' => [6, 'Junho'], 'junho' => [6, 'Junho'],
'jul' => [7, 'Julho'], 'julho' => [7, 'Julho'],
'ago' => [8, 'Agosto'], 'agosto' => [8, 'Agosto'],
'set' => [9, 'Setembro'], 'setembro' => [9, 'Setembro'],
'out' => [10, 'Outubro'], 'outubro' => [10, 'Outubro'],
'nov' => [11, 'Novembro'], 'novembro' => [11, 'Novembro'],
'dez' => [12, 'Dezembro'], 'dezembro' => [12, 'Dezembro']
}.freeze
# Agrupa as operações por mês/ano (extraído do nome da tabela), do mais recente
# para o mais antigo; as que não têm mês reconhecível caem em "Outras".
# Retorna um Array de [titulo_do_grupo, [{ tabela:, label: }, ...]].
def agrupadas_por_mes
grupos = Hash.new { |h, k| h[k] = [] }
ordem = {}
todas.each do |op|
g = grupo_de(op[:tabela])
grupos[g[:titulo]] << { tabela: op[:tabela], label: g[:item] }
ordem[g[:titulo]] ||= g[:ordem]
end
grupos.each_value { |itens| itens.sort_by! { |i| i[:label] } }
grupos.sort_by { |titulo, _| ordem[titulo] }.map { |titulo, itens| [titulo, itens] }
end
# Dados agregados das entregas das operações informadas, para pré-preencher o
# formulário de nova consolidação. Une as NFs de todas as tabelas válidas.
def dados(tabelas)
validas = sanitizar(tabelas)
return vazio if validas.empty?
entregas = Entrega.da_conta_gade.da_operacoes(validas)
pagas = entregas.pagas
{
motoristas: entregas.distinct.order(:driver).pluck(:driver).compact.reject(&:blank?),
veiculos: entregas.distinct.order(:vehicle).pluck(:vehicle).compact.reject(&:blank?),
# Período sugerido pela DATA REAL (checkout), igual ao filtro de
# elegibilidade; cai para planned_date se ainda não houver pagas.
inicio: (pagas.minimum(:checkout) || entregas.minimum(:planned_date))&.to_date,
fim: (pagas.maximum(:checkout) || entregas.maximum(:planned_date))&.to_date
}
end
private
# Extrai { titulo:, item:, ordem: } do nome da tabela.
# Ex.: gade_entregas_emad_fev_2026 → titulo "Fevereiro 2026", item "Emad".
def grupo_de(tabela)
tokens = tabela.to_s.delete_prefix(PREFIXO).split('_')
idx = tokens.index { |t| MESES.key?(t.downcase) }
if idx
num, nome = MESES[tokens[idx].downcase]
ano = tokens[idx + 1] =~ /\A\d{4}\z/ ? tokens[idx + 1].to_i : nil
resto = (tokens[0...idx] + tokens[(ano ? idx + 2 : idx + 1)..]).join(' ').strip
{
titulo: ano ? "#{nome} #{ano}" : nome,
item: (resto.presence || nome).to_s.upcase,
# mais recente primeiro; sem ano vai pro fim do bloco do mês
ordem: [-(ano || 0), -num]
}
else
{ titulo: 'Outras', item: label(tabela), ordem: [1, 0] }
end
end
def vazio
{ motoristas: [], veiculos: [], inicio: nil, fim: nil }
end
def conn
ActiveRecord::Base.connection
end
end
end