120 lines
4.6 KiB
Ruby
120 lines
4.6 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.
|
|
def label(tabela)
|
|
tabela.to_s.delete_prefix(PREFIXO).tr('_', ' ').strip.titleize
|
|
end
|
|
|
|
# Abreviações de mês usadas nos nomes das tabelas (gade_entregas_emad_fev_2026).
|
|
MESES = {
|
|
'jan' => [1, 'Janeiro'], 'fev' => [2, 'Fevereiro'], 'mar' => [3, 'Março'],
|
|
'abr' => [4, 'Abril'], 'mai' => [5, 'Maio'], 'jun' => [6, 'Junho'],
|
|
'jul' => [7, 'Julho'], 'ago' => [8, 'Agosto'], 'set' => [9, 'Setembro'],
|
|
'out' => [10, 'Outubro'], 'nov' => [11, 'Novembro'], 'dez' => [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&.titleize || nome,
|
|
# 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
|