Correção de horario e implantação da geração de Romaneio
This commit is contained in:
232
app/controllers/admin/romaneios_controller.rb
Normal file
232
app/controllers/admin/romaneios_controller.rb
Normal file
@@ -0,0 +1,232 @@
|
||||
# app/controllers/admin/romaneios_controller.rb
|
||||
#
|
||||
# Romaneio "CONTROLE DE ENTREGA": importa o plano do dia, deixa o operador revisar a
|
||||
# tabela com a prévia do PDF real ao lado, e imprime uma folha A4 paisagem por
|
||||
# veículo.
|
||||
#
|
||||
# As bases externas (espelho de rastreio e gade_entregas_*) continuam SOMENTE
|
||||
# LEITURA — o que gravamos são as tabelas `romaneio*`, nossas.
|
||||
class Admin::RomaneiosController < ApplicationController
|
||||
include Auditavel
|
||||
|
||||
before_action :carregar_romaneio, except: %i[index create importar_planilha]
|
||||
|
||||
def index
|
||||
authorize Romaneio
|
||||
# includes: a listagem mostra a contagem de veículos de cada romaneio.
|
||||
@romaneios = Romaneio.recentes.includes(:romaneio_veiculos).limit(50)
|
||||
@operacoes_agrupadas = Operacao.agrupadas_por_mes
|
||||
@data_padrao = Date.current
|
||||
end
|
||||
|
||||
def show
|
||||
authorize @romaneio
|
||||
@veiculos = @romaneio.romaneio_veiculos.ordenados.to_a
|
||||
@veiculo = veiculo_selecionado
|
||||
@linhas = @romaneio.linhas_vigentes.do_veiculo(@veiculo).to_a if @veiculo
|
||||
@removidas = @romaneio.linhas_removidas.to_a
|
||||
@colunas_ausentes = colunas_ausentes_da_operacao
|
||||
end
|
||||
|
||||
# Caminho A: busca o plano no SimpliRoute (API, com queda para o espelho).
|
||||
def create
|
||||
authorize Romaneio
|
||||
@romaneio = preparar_romaneio
|
||||
return if performed?
|
||||
|
||||
plano = Romaneios::PlanoDoDia.new(data: @romaneio.planned_date)
|
||||
importar(plano.linhas, origem: plano.origem)
|
||||
rescue SimpliRoute::Error => e
|
||||
# NotFound herda de Error: a mensagem dele já diz para enviar o .xlsx.
|
||||
descartar_romaneio_vazio
|
||||
redirect_to admin_romaneios_path, alert: e.message
|
||||
end
|
||||
|
||||
# Caminho B: upload do .xlsx do plano, para quando o plano do dia ainda não foi
|
||||
# publicado ou a API não responde.
|
||||
def importar_planilha
|
||||
authorize Romaneio
|
||||
@romaneio = preparar_romaneio
|
||||
return if performed?
|
||||
|
||||
arquivo = params[:arquivo]
|
||||
if arquivo.blank?
|
||||
descartar_romaneio_vazio
|
||||
return redirect_to(admin_romaneios_path, alert: 'Selecione a planilha do plano (.xlsx).')
|
||||
end
|
||||
|
||||
importar(Romaneios::PlanoPlanilha.new(arquivo).linhas, origem: 'planilha')
|
||||
rescue Romaneios::PlanoPlanilha::Erro => e
|
||||
descartar_romaneio_vazio
|
||||
redirect_to admin_romaneios_path, alert: e.message
|
||||
end
|
||||
|
||||
# Rebusca o plano e reconcilia com o que já está editado (a regra está em
|
||||
# Romaneios::Importador).
|
||||
def reimportar
|
||||
authorize @romaneio
|
||||
|
||||
# Reimportar com arquivo em mãos usa o arquivo; sem arquivo, volta ao
|
||||
# SimpliRoute — inclusive para um romaneio que nasceu de planilha (é justamente
|
||||
# assim que o operador troca o plano B pelo A quando a API volta).
|
||||
if params[:arquivo].present?
|
||||
plano = Romaneios::PlanoPlanilha.new(params[:arquivo])
|
||||
linhas = plano.linhas
|
||||
origem = 'planilha'
|
||||
else
|
||||
plano = Romaneios::PlanoDoDia.new(data: @romaneio.planned_date)
|
||||
linhas = plano.linhas
|
||||
origem = plano.origem
|
||||
end
|
||||
|
||||
resultado = aplicar(linhas, origem: origem)
|
||||
auditar!(:reimportar, @romaneio, dados_novos: resultado.to_h)
|
||||
redirect_to admin_romaneio_path(@romaneio, veiculo: params[:veiculo]),
|
||||
notice: "Plano reimportado — #{resultado.resumo}."
|
||||
rescue SimpliRoute::Error, Romaneios::PlanoPlanilha::Erro => e
|
||||
redirect_to admin_romaneio_path(@romaneio, veiculo: params[:veiculo]), alert: e.message
|
||||
end
|
||||
|
||||
# Com ?veiculo= sai INLINE (é a prévia do editor, uma folha só, sub-100ms);
|
||||
# sem veiculo sai o romaneio inteiro como download.
|
||||
def pdf
|
||||
authorize @romaneio
|
||||
|
||||
veiculo = params[:veiculo].to_s
|
||||
binario = Pdf::RomaneioPdf.new(romaneio: @romaneio,
|
||||
veiculos: veiculo.presence && [veiculo]).render
|
||||
|
||||
# A prévia é regerada a cada edição: cache do navegador aqui devolveria o PDF
|
||||
# anterior e o operador acharia que a alteração não gravou.
|
||||
expires_now if veiculo.present?
|
||||
|
||||
send_data binario, type: 'application/pdf',
|
||||
disposition: veiculo.present? ? 'inline' : 'attachment',
|
||||
filename: nome_do_arquivo(veiculo)
|
||||
end
|
||||
|
||||
# JSON — grava UM campo de UMA parada. `valor: null` restaura o que o plano
|
||||
# trouxe (e destrava o campo para a próxima reimportação).
|
||||
def atualizar_linha
|
||||
authorize @romaneio
|
||||
|
||||
linha = @romaneio.romaneio_linhas.find(params[:linha_id])
|
||||
campo = params[:campo].to_s
|
||||
unless RomaneioLinha::CAMPOS_EDITAVEIS.include?(campo)
|
||||
return render json: { ok: false, erro: 'Campo não editável.' }, status: :unprocessable_entity
|
||||
end
|
||||
|
||||
if params[:valor].nil?
|
||||
linha[campo] = linha.valor_do_plano(campo)
|
||||
linha.desmarcar_editado!(campo)
|
||||
else
|
||||
linha[campo] = params[:valor].to_s
|
||||
linha.marcar_editado!(campo)
|
||||
end
|
||||
linha.save!
|
||||
|
||||
# Devolvemos o valor GRAVADO e o JS o reescreve no input: a gravação é
|
||||
# last-write-wins, e assim uma sobreposição de duas abas fica visível em vez
|
||||
# de silenciosa.
|
||||
render json: { ok: true, valor: linha[campo], editado: linha.editado?(campo) }
|
||||
end
|
||||
|
||||
def atualizar_veiculo
|
||||
authorize @romaneio
|
||||
|
||||
registro = @romaneio.romaneio_veiculos.find_by!(veiculo: params[:veiculo].to_s)
|
||||
registro.update!(motorista: params[:motorista].to_s)
|
||||
render json: { ok: true, valor: registro.motorista }
|
||||
end
|
||||
|
||||
def atualizar_rotulo
|
||||
authorize @romaneio
|
||||
|
||||
@romaneio.update!(rotulo_plano: params[:rotulo_plano].to_s)
|
||||
render json: { ok: true, valor: @romaneio.rotulo_plano }
|
||||
end
|
||||
|
||||
def destroy
|
||||
authorize @romaneio
|
||||
|
||||
# Auditar ANTES: depois do destroy não há mais atributos para registrar.
|
||||
auditar!(:excluir, @romaneio, dados_anteriores: @romaneio.attributes)
|
||||
@romaneio.destroy!
|
||||
redirect_to admin_romaneios_path, notice: 'Romaneio excluído.'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def carregar_romaneio
|
||||
@romaneio = Romaneio.find(params[:id])
|
||||
end
|
||||
|
||||
# Quais colunas a operação NÃO tem. Perguntamos ao serviço em vez de deduzir de
|
||||
# "a coluna APARELHO saiu toda vazia": vazio também é o resultado legítimo de uma
|
||||
# operação só com pacientes recorrentes, e um aviso que erra o motivo é pior do
|
||||
# que nenhum.
|
||||
def colunas_ausentes_da_operacao
|
||||
return [] if @romaneio.operacao_tabela.blank?
|
||||
|
||||
Romaneios::EnriquecimentoGade.new(@romaneio.operacao_tabela).colunas_ausentes
|
||||
rescue ArgumentError
|
||||
# Operação removida do banco depois que o romaneio foi criado.
|
||||
Romaneios::EnriquecimentoGade::COLUNAS.keys
|
||||
end
|
||||
|
||||
# Acha (ou cria) o romaneio da data + operação. `find_or_create_by!` é o que faz
|
||||
# "importar de novo o mesmo dia" cair na reconciliação em vez de criar um segundo
|
||||
# romaneio — a chave única (planned_date, operacao_tabela) é a garantia disso.
|
||||
def preparar_romaneio
|
||||
data = params[:planned_date].presence
|
||||
if data.blank?
|
||||
redirect_to admin_romaneios_path, alert: 'Informe a data do plano.'
|
||||
return nil
|
||||
end
|
||||
|
||||
tabela = params[:operacao_tabela].to_s
|
||||
if tabela.present? && !Operacao.valida?(tabela)
|
||||
redirect_to admin_romaneios_path, alert: 'Selecione uma operação válida.'
|
||||
return nil
|
||||
end
|
||||
|
||||
data = Date.parse(data)
|
||||
Romaneio.find_or_create_by!(planned_date: data, operacao_tabela: tabela) do |r|
|
||||
r.rotulo_plano = params[:rotulo_plano].presence || Romaneio.rotulo_sugerido(tabela, data)
|
||||
r.importado_por = current_user
|
||||
end
|
||||
rescue Date::Error
|
||||
redirect_to admin_romaneios_path, alert: 'Data do plano inválida.'
|
||||
nil
|
||||
end
|
||||
|
||||
def importar(linhas, origem:)
|
||||
resultado = aplicar(linhas, origem: origem)
|
||||
auditar!(:criar, @romaneio, dados_novos: resultado.to_h)
|
||||
redirect_to admin_romaneio_path(@romaneio),
|
||||
notice: "Plano importado — #{resultado.resumo}."
|
||||
end
|
||||
|
||||
def aplicar(linhas, origem:)
|
||||
Romaneios::Importador.new(romaneio: @romaneio, linhas: linhas,
|
||||
usuario: current_user, origem: origem).aplicar!
|
||||
end
|
||||
|
||||
# Um romaneio recém-criado que não chegou a receber nenhuma parada é lixo: some
|
||||
# da lista para o operador não achar que a importação funcionou.
|
||||
def descartar_romaneio_vazio
|
||||
@romaneio&.destroy! if @romaneio&.persisted? && @romaneio.romaneio_linhas.none?
|
||||
end
|
||||
|
||||
def veiculo_selecionado
|
||||
pedido = params[:veiculo].to_s
|
||||
return pedido if @veiculos.any? { |v| v.veiculo == pedido }
|
||||
|
||||
@veiculos.first&.veiculo
|
||||
end
|
||||
|
||||
def nome_do_arquivo(veiculo)
|
||||
partes = ['romaneio', @romaneio.rotulo_plano.presence || @romaneio.planned_date.to_s, veiculo]
|
||||
"#{partes.compact_blank.join('_').parameterize(separator: '_')}.pdf"
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user