Adição do botão de entrega manual

This commit is contained in:
2026-06-25 11:35:41 -03:00
parent 022aac2616
commit 0c5b5bcbe6
22 changed files with 615 additions and 67 deletions

View File

@@ -31,6 +31,9 @@ class ConsolidacaoEntregasController < ApplicationController
@total_entregas = @consolidacao.entregas_elegiveis_count(@motorista)
@classificadas = @consolidacao.classificadas_count(@motorista)
# Nomes dos motoristas ATIVOS — alimenta o seletor do apontamento 100% manual.
@motoristas_consolidacao = @consolidacao.consolidacao_motoristas.ativos.pluck(:motorista_nome)
# Filtro "apenas não classificadas"
if params[:apenas_pendentes] == '1'
@entregas = @entregas.reject { |e| @classificacoes.key?(e.tracking_id) }
@@ -157,8 +160,8 @@ class ConsolidacaoEntregasController < ApplicationController
@pagy, @classificacoes = pagy(todas.order(:created_at), limit: @por_pagina, page: 1)
end
# Próximo motorista pendente (para botão "Próximo Motorista")
nomes_completos = @consolidacao.consolidacao_motoristas.map(&:motorista_nome)
# Próximo motorista pendente (para botão "Próximo Motorista") — só ativos
nomes_completos = @consolidacao.consolidacao_motoristas.ativos.map(&:motorista_nome)
@proximo = nomes_completos.find do |nome|
next false if nome == @motorista
total = @consolidacao.entregas_elegiveis_count(nome)
@@ -207,6 +210,36 @@ class ConsolidacaoEntregasController < ApplicationController
render_apontamento_erro(e.message)
end
# POST /consolidacoes/:consolidacao_id/consolidacao_entregas/apontar_manual
# Apontamento 100% MANUAL — não consulta a base de rastreio. O admin informa o
# motorista (pode ser outro que não o da tela), a NF (texto livre), uma
# observação/local e os pilares. Usado quando o motorista foi ao local mas a
# NF ficou registrada com outro motorista no rastreio.
def apontar_manual
authorize @consolidacao, :update?
motorista = @consolidacao.adicionar_apontamento_manual!(
motorista: params[:motorista].to_s.strip,
tipos: tipos_param,
nf: params[:nf],
obs: params[:obs],
valor_extra: valor_extraordinaria_param,
user: current_user
)
auditar!(:editar, @consolidacao,
dados_novos: { apontamento_100_manual: params[:nf], motorista: motorista, tipos: tipos_param })
respond_to do |format|
format.json { render json: resumo_json(motorista).merge(motorista: motorista) }
format.html do
redirect_to validar_consolidacao_consolidacao_entregas_path(@consolidacao, motorista: motorista),
notice: "Apontamento manual adicionado para #{motorista}."
end
end
rescue ArgumentError => e
render_apontamento_erro(e.message)
end
# DELETE /consolidacoes/:consolidacao_id/consolidacao_entregas/remover_apontamento
# Remove um apontamento manual — por ?id= (um pilar) ou por ?tracking_id=&motorista=
# (todos os pilares daquela nota). Não passa pelo guard de elegibilidade, pois o
@@ -246,9 +279,9 @@ class ConsolidacaoEntregasController < ApplicationController
redirect_to @consolidacao, alert: 'Consolidação finalizada — apenas admin/gerente podem editar.'
end
# Motorista realmente faz parte desta consolidação?
# Motorista realmente faz parte desta consolidação? (ignora arquivados)
def motorista_na_consolidacao?(nome)
nome.present? && @consolidacao.consolidacao_motoristas.exists?(motorista_nome: nome)
nome.present? && @consolidacao.consolidacao_motoristas.ativos.exists?(motorista_nome: nome)
end
# tracking_ids atendidos/elegíveis para o motorista no período (respeita veículos).
@@ -311,6 +344,17 @@ class ConsolidacaoEntregasController < ApplicationController
valor.present? && valor >= 0 ? valor : Configuracao.preco_extraordinaria
end
# Valor customizado da Extraordinária vindo do formulário (params[:valor]),
# aceitando vírgula decimal. Retorna nil se ausente/inválido — aí o model usa
# o preço padrão de Configurações.
def valor_extraordinaria_param
bruto = params[:valor].to_s.strip.tr(',', '.')
return nil if bruto.blank?
valor = BigDecimal(bruto) rescue nil
valor if valor && valor >= 0
end
def recalcular_motorista(nome)
@consolidacao.recalcular_motorista!(nome)
end

View File

@@ -5,6 +5,7 @@ class ConsolidacoesController < ApplicationController
before_action :set_consolidacao,
only: %i[show edit update destroy finalizar arquivar reativar wizard
registrar_pagamento cancelar_pagamento
arquivar_motorista desarquivar_motorista
preview_extrato gerar_pdf_relatorio gerar_pdf_extrato
gerar_pdf_financeiro]
@@ -151,7 +152,8 @@ class ConsolidacoesController < ApplicationController
# GET /consolidacoes/:id — visão geral
def show
authorize @consolidacao
@motoristas = @consolidacao.consolidacao_motoristas.order(:motorista_nome)
# A gestão de arquivamento de motorista fica no wizard; aqui listamos só os ativos.
@motoristas = @consolidacao.consolidacao_motoristas.ativos.order(:motorista_nome)
# Apontamentos manuais agrupados por nota (tracking) p/ exibição e remoção.
@apontamentos = @consolidacao.consolidacao_entregas.apontamentos
.order(:motorista_nome, :tracking_id)
@@ -162,7 +164,8 @@ class ConsolidacoesController < ApplicationController
def wizard
authorize @consolidacao, :update?
@motoristas = @consolidacao.consolidacao_motoristas.order(:motorista_nome).map do |cm|
@arquivados = @consolidacao.consolidacao_motoristas.arquivados.order(:motorista_nome)
@motoristas = @consolidacao.consolidacao_motoristas.ativos.order(:motorista_nome).map do |cm|
total = @consolidacao.entregas_elegiveis_count(cm.motorista_nome)
classificadas = @consolidacao.classificadas_count(cm.motorista_nome)
apontamentos = @consolidacao.consolidacao_entregas
@@ -265,6 +268,37 @@ class ConsolidacoesController < ApplicationController
redirect_to @consolidacao, notice: pagamento_notice(alvos.size, :estornado)
end
# POST /consolidacoes/:id/arquivar_motorista — arquiva 1 motorista da
# consolidação (reversível). Ele sai dos totais/pagamento/gate, mas os dados
# ficam guardados e podem ser restaurados. Não arquiva motorista já pago.
def arquivar_motorista
authorize @consolidacao, :update?
cm = @consolidacao.consolidacao_motoristas.find(params[:consolidacao_motorista_id])
if cm.pago?
return redirect_to wizard_consolidacao_path(@consolidacao),
alert: "#{cm.motorista_nome} já está pago — estorne o pagamento antes de arquivar."
end
cm.arquivar!(current_user)
@consolidacao.recalcular_motorista!(cm.motorista_nome) # tira o valor do total
auditar!(:editar, @consolidacao, dados_novos: { arquivar_motorista: cm.motorista_nome })
redirect_to wizard_consolidacao_path(@consolidacao),
notice: "#{cm.motorista_nome} arquivado. Você pode restaurar quando quiser."
end
# POST /consolidacoes/:id/desarquivar_motorista — restaura um motorista arquivado.
def desarquivar_motorista
authorize @consolidacao, :update?
cm = @consolidacao.consolidacao_motoristas.find(params[:consolidacao_motorista_id])
cm.desarquivar!(current_user)
@consolidacao.recalcular_motorista!(cm.motorista_nome) # devolve o valor ao total
auditar!(:editar, @consolidacao, dados_novos: { desarquivar_motorista: cm.motorista_nome })
redirect_to wizard_consolidacao_path(@consolidacao),
notice: "#{cm.motorista_nome} restaurado para a consolidação."
end
# ── FASE 7 — PDFs ────────────────────────────────────────────
# GET /consolidacoes/:id/gerar_pdf_relatorio?motorista=X
@@ -358,11 +392,12 @@ class ConsolidacoesController < ApplicationController
end
# Um motorista específico (consolidacao_motorista_id) ou todos da consolidação.
# "Todos" cobre apenas os ATIVOS — motorista arquivado não recebe pagamento.
def motoristas_alvo
if params[:consolidacao_motorista_id].present?
[@consolidacao.consolidacao_motoristas.find(params[:consolidacao_motorista_id])]
else
@consolidacao.consolidacao_motoristas.to_a
@consolidacao.consolidacao_motoristas.ativos.to_a
end
end

View File

@@ -146,7 +146,7 @@ class DashboardController < ApplicationController
# PAGOS: filtrados pela DATA DO PAGAMENTO (pago_em) dentro do período do topo
# — "quanto foi pago neste período".
pagos = ConsolidacaoMotorista
pagos = ConsolidacaoMotorista.ativos
.joins(:consolidacao)
.where(consolidacoes: { status: :finalizada, deleted_at: nil })
.where(pago_em: intervalo)
@@ -159,7 +159,7 @@ class DashboardController < ApplicationController
# PENDENTES: consolidações finalizadas cujo PERÍODO cruza o filtro, ainda sem
# pagamento (pendente não tem data de pagamento para filtrar).
finalizadas_ids = @consolidacoes_mes.where(status: :finalizada).select(:id)
pendentes = ConsolidacaoMotorista.where(consolidacao_id: finalizadas_ids).pendentes
pendentes = ConsolidacaoMotorista.ativos.where(consolidacao_id: finalizadas_ids).pendentes
@pag_pend_qtd = pendentes.count
@pag_pend_valor = pendentes.sum(:valor_total)
@@ -179,13 +179,13 @@ class DashboardController < ApplicationController
# algum pagamento feito dentro do período. Sem o 2º caso, pagar uma
# consolidação de outro mês não mexia nos gráficos (só na tabela de pagamentos).
ids_periodo = base.where('data_inicio <= ? AND data_fim >= ?', @periodo_fim, @periodo_inicio).ids
ids_pagos = ConsolidacaoMotorista.where(pago_em: intervalo)
ids_pagos = ConsolidacaoMotorista.ativos.where(pago_em: intervalo)
.where(consolidacao_id: base.select(:id))
.distinct.pluck(:consolidacao_id)
consols = Consolidacao.where(id: (ids_periodo + ids_pagos).uniq).to_a
ids = consols.map(&:id)
cms = ConsolidacaoMotorista.where(consolidacao_id: ids)
cms = ConsolidacaoMotorista.ativos.where(consolidacao_id: ids)
ces = ConsolidacaoEntrega.where(consolidacao_id: ids)
# KPIs

View File

@@ -20,11 +20,11 @@ module Motorista
# Card 2 — Valor CONSOLIDADO: soma das consolidações finalizadas
@minhas_consolidacoes = Consolidacao.ativas
.joins(:consolidacao_motoristas)
.where(consolidacao_motoristas: { motorista_nome: nome })
.where(consolidacao_motoristas: { motorista_nome: nome, arquivado_em: nil })
.recentes
.distinct
@valor_consolidado = ConsolidacaoMotorista
@valor_consolidado = ConsolidacaoMotorista.ativos
.joins(:consolidacao)
.where(motorista_nome: nome,
consolidacoes: { status: :finalizada, deleted_at: nil })