teste #5
BIN
Imagens para correção /Imagem colada (3).png
Normal file
BIN
Imagens para correção /Imagem colada (3).png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 152 KiB |
BIN
Imagens para correção /Imagem colada (4).png
Normal file
BIN
Imagens para correção /Imagem colada (4).png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 141 KiB |
@@ -464,36 +464,9 @@ class ConsolidacaoEntregasController < ApplicationController
|
|||||||
# { vehicle:, eligible:, classificadas:, fechado: }. Fonte única do filtro de
|
# { vehicle:, eligible:, classificadas:, fechado: }. Fonte única do filtro de
|
||||||
# carros e do acompanhamento (chips). 2 queries: elegíveis (com a coluna do
|
# carros e do acompanhamento (chips). 2 queries: elegíveis (com a coluna do
|
||||||
# veículo) e as já classificadas (não-manuais).
|
# veículo) e as já classificadas (não-manuais).
|
||||||
# Status por veículo do motorista: { vehicle, eligible, classificadas, completo,
|
# Status por veículo do motorista (fonte única no model: Consolidacao#veiculos_do_motorista).
|
||||||
# fechado, arquivado }. `completo` = 100% classificado (verde, pode consolidar);
|
|
||||||
# `fechado` = veículo consolidado (cadeado, vem de consolidacao_veiculos);
|
|
||||||
# `arquivado` = fora do gate. Une os veículos com entregas elegíveis (rastreio)
|
|
||||||
# com os que só existem como linha de consolidacao_veiculos (arquivados/manuais).
|
|
||||||
def veiculos_status(motorista)
|
def veiculos_status(motorista)
|
||||||
return [] unless motorista.present?
|
@consolidacao.veiculos_do_motorista(motorista)
|
||||||
|
|
||||||
rel = Entrega.atendidas.da_conta_gade
|
|
||||||
.no_periodo_checkout(@consolidacao.data_inicio, @consolidacao.data_fim)
|
|
||||||
.do_motorista(motorista)
|
|
||||||
rel = rel.da_veiculo(@consolidacao.vehicle_ids) if @consolidacao.vehicle_ids.present?
|
|
||||||
rel = rel.da_operacoes(@consolidacao.operacoes) if @consolidacao.operacoes.present?
|
|
||||||
|
|
||||||
por_veiculo = rel.pluck(:tracking_id, :vehicle).group_by { |(_id, v)| v.presence || SEM_VEICULO }
|
|
||||||
classificadas = @consolidacao.consolidacao_entregas
|
|
||||||
.where(motorista_nome: motorista, manual: false)
|
|
||||||
.distinct.pluck(:tracking_id).to_set
|
|
||||||
estados = @consolidacao.consolidacao_veiculos.where(motorista_nome: motorista).index_by(&:vehicle)
|
|
||||||
|
|
||||||
veiculos = (por_veiculo.keys + estados.keys).uniq
|
|
||||||
veiculos.map do |veiculo|
|
|
||||||
ids = (por_veiculo[veiculo] || []).map(&:first)
|
|
||||||
feito = ids.count { |id| classificadas.include?(id) }
|
|
||||||
cv = estados[veiculo]
|
|
||||||
{ vehicle: veiculo, eligible: ids.size, classificadas: feito,
|
|
||||||
completo: ids.size.positive? && feito >= ids.size,
|
|
||||||
fechado: cv&.fechado? || false,
|
|
||||||
arquivado: cv&.arquivado? || false }
|
|
||||||
end.sort_by { |v| v[:vehicle] }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Valida que a entrega pertence ao motorista informado e ao período/veículo da
|
# Valida que a entrega pertence ao motorista informado e ao período/veículo da
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ class ConsolidacoesController < ApplicationController
|
|||||||
include Auditavel
|
include Auditavel
|
||||||
|
|
||||||
before_action :set_consolidacao,
|
before_action :set_consolidacao,
|
||||||
only: %i[show edit update destroy finalizar arquivar reativar wizard
|
only: %i[show edit update destroy finalizar finalizar_parcial arquivar reativar wizard
|
||||||
registrar_pagamento cancelar_pagamento
|
registrar_pagamento cancelar_pagamento
|
||||||
arquivar_motorista desarquivar_motorista
|
arquivar_motorista desarquivar_motorista
|
||||||
excluir_motorista adicionar_motorista
|
excluir_motorista adicionar_motorista
|
||||||
@@ -181,19 +181,19 @@ class ConsolidacoesController < ApplicationController
|
|||||||
.apontamentos
|
.apontamentos
|
||||||
.where(motorista_nome: cm.motorista_nome)
|
.where(motorista_nome: cm.motorista_nome)
|
||||||
.distinct.count(:tracking_id)
|
.distinct.count(:tracking_id)
|
||||||
# Status por veículo (para o breakdown do card) e estado de fechamento.
|
# Status por veículo (breakdown do card): total usados, consolidados (🔒),
|
||||||
veiculos = @consolidacao.consolidacao_veiculos.ativos
|
# pendentes (não dá p/ consolidar ainda) e prontos (100%, falta consolidar).
|
||||||
.where(motorista_nome: cm.motorista_nome)
|
vinfo = @consolidacao.veiculos_do_motorista(cm.motorista_nome).reject { |v| v[:arquivado] }
|
||||||
.order(:vehicle)
|
|
||||||
{
|
{
|
||||||
registro: cm,
|
registro: cm,
|
||||||
total: total,
|
total: total,
|
||||||
classificadas: classificadas,
|
classificadas: classificadas,
|
||||||
apontamentos: apontamentos,
|
apontamentos: apontamentos,
|
||||||
veiculos: veiculos,
|
veiculos_total: vinfo.size,
|
||||||
veiculos_fechados: veiculos.count(&:fechado?),
|
veiculos_fechados: vinfo.count { |v| v[:fechado] },
|
||||||
# Espelha o gate do servidor (todas_entregas_classificadas?): exige tudo
|
veiculos_prontos: vinfo.count { |v| !v[:fechado] && v[:completo] },
|
||||||
# classificado E todos os veículos ativos fechados (consolidados).
|
veiculos_pendentes: vinfo.select { |v| !v[:fechado] && !v[:completo] && v[:eligible].positive? }
|
||||||
|
.map { |v| v[:vehicle] },
|
||||||
completo: @consolidacao.motorista_completo?(cm.motorista_nome)
|
completo: @consolidacao.motorista_completo?(cm.motorista_nome)
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
@@ -213,6 +213,25 @@ class ConsolidacoesController < ApplicationController
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# POST /consolidacoes/:id/finalizar_parcial
|
||||||
|
# Atalho "finalizar só os veículos consolidados": arquiva os veículos que ainda
|
||||||
|
# não dá para consolidar (não 100% classificados) e finaliza a consolidação. Os
|
||||||
|
# veículos consolidados/prontos seguem no fechamento; os pendentes ficam de fora.
|
||||||
|
def finalizar_parcial
|
||||||
|
authorize @consolidacao, :update?
|
||||||
|
|
||||||
|
arquivados = @consolidacao.arquivar_veiculos_pendentes!(current_user)
|
||||||
|
if @consolidacao.finalizar!(current_user)
|
||||||
|
auditar!(:finalizar, @consolidacao, dados_novos: { finalizar_parcial: true, veiculos_arquivados: arquivados })
|
||||||
|
NotificacaoService.notificar_finalizacao(@consolidacao)
|
||||||
|
redirect_to @consolidacao,
|
||||||
|
notice: "Consolidação finalizada. #{arquivados.size} veículo(s) pendente(s) ficaram de fora. Motoristas notificados."
|
||||||
|
else
|
||||||
|
redirect_to wizard_consolidacao_path(@consolidacao),
|
||||||
|
alert: 'Não foi possível finalizar: ainda há entregas pendentes fora dos veículos arquivados.'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# POST /consolidacoes/:id/arquivar
|
# POST /consolidacoes/:id/arquivar
|
||||||
def arquivar
|
def arquivar
|
||||||
authorize @consolidacao, :destroy?
|
authorize @consolidacao, :destroy?
|
||||||
|
|||||||
@@ -208,6 +208,54 @@ class Consolidacao < ApplicationRecord
|
|||||||
.distinct.count(:tracking_id)
|
.distinct.count(:tracking_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Status por veículo do motorista: array de { vehicle, eligible, classificadas,
|
||||||
|
# completo, fechado, arquivado }. `completo` = 100% classificado (pode consolidar);
|
||||||
|
# `fechado` = veículo consolidado (🔒); `arquivado` = fora do gate. Une os veículos
|
||||||
|
# com entregas elegíveis (rastreio) com os que só têm linha em consolidacao_veiculos
|
||||||
|
# (arquivados / buckets manuais). Fonte única do filtro de carros e do wizard.
|
||||||
|
def veiculos_do_motorista(motorista)
|
||||||
|
return [] unless motorista.present?
|
||||||
|
|
||||||
|
rel = Entrega.atendidas.da_conta_gade
|
||||||
|
.no_periodo_checkout(data_inicio, data_fim)
|
||||||
|
.do_motorista(motorista)
|
||||||
|
rel = rel.da_veiculo(vehicle_ids) if vehicle_ids.present?
|
||||||
|
rel = rel.da_operacoes(operacoes) if operacoes.present?
|
||||||
|
|
||||||
|
por_veiculo = rel.pluck(:tracking_id, :vehicle).group_by { |(_id, v)| v.presence || SEM_VEICULO }
|
||||||
|
classificadas = consolidacao_entregas.where(motorista_nome: motorista, manual: false)
|
||||||
|
.distinct.pluck(:tracking_id).to_set
|
||||||
|
estados = consolidacao_veiculos.where(motorista_nome: motorista).index_by(&:vehicle)
|
||||||
|
|
||||||
|
(por_veiculo.keys + estados.keys).uniq.map do |veiculo|
|
||||||
|
ids = (por_veiculo[veiculo] || []).map(&:first)
|
||||||
|
feito = ids.count { |id| classificadas.include?(id) }
|
||||||
|
cv = estados[veiculo]
|
||||||
|
{ vehicle: veiculo, eligible: ids.size, classificadas: feito,
|
||||||
|
completo: ids.size.positive? && feito >= ids.size,
|
||||||
|
fechado: cv&.fechado? || false,
|
||||||
|
arquivado: cv&.arquivado? || false }
|
||||||
|
end.sort_by { |v| v[:vehicle] }
|
||||||
|
end
|
||||||
|
|
||||||
|
# Arquiva os veículos PENDENTES (com entregas elegíveis mas não 100% classificadas)
|
||||||
|
# de todos os motoristas ativos, deixando-os de fora da finalização. Usado pelo
|
||||||
|
# atalho "arquivar pendentes e finalizar" — os prontos/consolidados seguem no todo.
|
||||||
|
# Retorna a lista "Motorista · Veículo" arquivada.
|
||||||
|
def arquivar_veiculos_pendentes!(user)
|
||||||
|
arquivados = []
|
||||||
|
consolidacao_motoristas.ativos.each do |cm|
|
||||||
|
veiculos_do_motorista(cm.motorista_nome).each do |v|
|
||||||
|
next if v[:fechado] || v[:arquivado] || v[:completo] || v[:eligible].zero?
|
||||||
|
|
||||||
|
cv = consolidacao_veiculos.find_or_create_by!(motorista_nome: cm.motorista_nome, vehicle: v[:vehicle])
|
||||||
|
cv.arquivar!(user)
|
||||||
|
arquivados << "#{cm.motorista_nome} · #{v[:vehicle]}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
arquivados
|
||||||
|
end
|
||||||
|
|
||||||
# ── Apontamento manual ──────────────────────────────────────
|
# ── Apontamento manual ──────────────────────────────────────
|
||||||
# Adiciona uma entrega "por fora" (NF que chegou depois) com um ou mais pilares
|
# Adiciona uma entrega "por fora" (NF que chegou depois) com um ou mais pilares
|
||||||
# (tipos). O motorista vem da própria entrega e é incluído na consolidação se
|
# (tipos). O motorista vem da própria entrega e é incluído na consolidação se
|
||||||
|
|||||||
@@ -187,11 +187,14 @@
|
|||||||
· <span class="text-orange-400">➕ <%= m[:apontamentos] %> apontamento<%= 's' if m[:apontamentos] > 1 %></span>
|
· <span class="text-orange-400">➕ <%= m[:apontamentos] %> apontamento<%= 's' if m[:apontamentos] > 1 %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
<% if m[:veiculos].any? %>
|
<% if m[:veiculos_total] > 0 %>
|
||||||
<p class="text-xs mt-0.5">
|
<p class="text-xs mt-0.5">
|
||||||
<span class="<%= m[:veiculos_fechados] == m[:veiculos].size ? 'text-green-400' : 'text-gray-400' %>">
|
<span class="<%= m[:veiculos_fechados] == m[:veiculos_total] ? 'text-green-400' : 'text-gray-400' %>">
|
||||||
🚗 <%= m[:veiculos_fechados] %>/<%= m[:veiculos].size %> veículo<%= 's' if m[:veiculos].size > 1 %> consolidado<%= 's' if m[:veiculos_fechados] != 1 %>
|
🚗 <%= m[:veiculos_fechados] %>/<%= m[:veiculos_total] %> veículo<%= 's' if m[:veiculos_total] > 1 %> consolidado<%= 's' if m[:veiculos_fechados] != 1 %>
|
||||||
</span>
|
</span>
|
||||||
|
<% if m[:veiculos_pendentes].any? %>
|
||||||
|
<span class="text-orange-400">· pendente<%= 's' if m[:veiculos_pendentes].size > 1 %>: <%= m[:veiculos_pendentes].join(', ') %></span>
|
||||||
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
@@ -255,23 +258,39 @@
|
|||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%# Finalizar — só habilita se tudo classificado %>
|
<%# Finalizar — habilita quando tudo classificado. Se sobram veículos pendentes
|
||||||
|
mas já há veículos consolidados, oferece "arquivar pendentes e finalizar". %>
|
||||||
<% tudo_completo = @motoristas.all? { |m| m[:completo] } && @motoristas.any? %>
|
<% tudo_completo = @motoristas.all? { |m| m[:completo] } && @motoristas.any? %>
|
||||||
<div class="mt-8 bg-[#1a1a1a] border border-[#2a2a2a] rounded-xl p-5 flex items-center justify-between">
|
<% tem_consolidados = @motoristas.any? { |m| m[:veiculos_fechados] > 0 || m[:veiculos_prontos] > 0 } %>
|
||||||
|
<% tem_pendentes = @motoristas.any? { |m| m[:veiculos_pendentes].any? } %>
|
||||||
|
<div class="mt-8 bg-[#1a1a1a] border border-[#2a2a2a] rounded-xl p-5">
|
||||||
|
<div class="flex items-center justify-between gap-3 flex-wrap">
|
||||||
<div>
|
<div>
|
||||||
<p class="text-white font-bold">Total da consolidação</p>
|
<p class="text-white font-bold">Total da consolidação</p>
|
||||||
<p class="text-orange-500 font-black text-3xl"><%= moeda(@consolidacao.valor_total) %></p>
|
<p class="text-orange-500 font-black text-3xl"><%= moeda(@consolidacao.valor_total) %></p>
|
||||||
</div>
|
</div>
|
||||||
<% if @consolidacao.rascunho? %>
|
<% if @consolidacao.rascunho? %>
|
||||||
|
<div class="flex flex-col sm:flex-row gap-2">
|
||||||
|
<% if !tudo_completo && tem_consolidados && tem_pendentes %>
|
||||||
|
<%= button_to '🔒 Arquivar pendentes e finalizar',
|
||||||
|
finalizar_parcial_consolidacao_path(@consolidacao),
|
||||||
|
method: :post,
|
||||||
|
data: { turbo_confirm: 'Arquivar TODOS os veículos ainda não classificados 100% e finalizar? Eles ficam de fora desta consolidação (você resolve depois). Os veículos consolidados são pagos normalmente.' },
|
||||||
|
class: 'bg-orange-500 hover:bg-orange-600 text-black font-bold px-5 py-3.5 rounded-lg min-h-[48px] flex items-center justify-center text-center' %>
|
||||||
|
<% end %>
|
||||||
<%= button_to 'Finalizar consolidação ✓', finalizar_consolidacao_path(@consolidacao),
|
<%= button_to 'Finalizar consolidação ✓', finalizar_consolidacao_path(@consolidacao),
|
||||||
method: :post,
|
method: :post,
|
||||||
disabled: !tudo_completo,
|
disabled: !tudo_completo,
|
||||||
data: { turbo_confirm: 'Finalizar? Após finalizada, a consolidação não poderá ser editada por operadores.' },
|
data: { turbo_confirm: 'Finalizar? Após finalizada, a consolidação não poderá ser editada por operadores.' },
|
||||||
class: "font-bold px-6 py-3.5 rounded-lg min-h-[48px] transition-colors
|
class: "font-bold px-6 py-3.5 rounded-lg min-h-[48px] transition-colors
|
||||||
#{tudo_completo ? 'bg-green-600 hover:bg-green-500 text-white cursor-pointer' : 'bg-[#2a2a2a] text-gray-600 cursor-not-allowed'}" %>
|
#{tudo_completo ? 'bg-green-600 hover:bg-green-500 text-white cursor-pointer' : 'bg-[#2a2a2a] text-gray-600 cursor-not-allowed'}" %>
|
||||||
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<% unless tudo_completo %>
|
<% unless tudo_completo %>
|
||||||
<p class="text-gray-500 text-sm mt-2 text-center">⚠️ Classifique todas as entregas de todos os motoristas para poder finalizar.</p>
|
<p class="text-gray-500 text-sm mt-2 text-center">
|
||||||
|
⚠️ Classifique todas as entregas de todos os motoristas para finalizar tudo<% if tem_consolidados && tem_pendentes %> — ou use <strong>“Arquivar pendentes e finalizar”</strong> para fechar só os veículos já consolidados.<% end %>
|
||||||
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ Rails.application.routes.draw do
|
|||||||
member do
|
member do
|
||||||
get :wizard # Passo 1 — selecionar motorista
|
get :wizard # Passo 1 — selecionar motorista
|
||||||
post :finalizar
|
post :finalizar
|
||||||
|
post :finalizar_parcial # arquiva veículos pendentes e finaliza (só os consolidados)
|
||||||
post :arquivar
|
post :arquivar
|
||||||
post :reativar # Restaura uma consolidação arquivada
|
post :reativar # Restaura uma consolidação arquivada
|
||||||
post :registrar_pagamento # Marca 1 motorista (ou todos) como pago
|
post :registrar_pagamento # Marca 1 motorista (ou todos) como pago
|
||||||
|
|||||||
@@ -293,6 +293,29 @@ RSpec.describe Consolidacao, type: :model do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe '#arquivar_veiculos_pendentes!' do
|
||||||
|
let(:consolidacao) { create(:consolidacao) }
|
||||||
|
let(:gerente) { create(:gerente) }
|
||||||
|
|
||||||
|
before { create(:consolidacao_motorista, consolidacao: consolidacao, motorista_nome: 'A') }
|
||||||
|
|
||||||
|
it 'arquiva só os veículos pendentes (com elegíveis e < 100%)' do
|
||||||
|
allow(consolidacao).to receive(:veiculos_do_motorista).with('A').and_return([
|
||||||
|
{ vehicle: 'GADE_1', eligible: 5, classificadas: 5, completo: true, fechado: true, arquivado: false },
|
||||||
|
{ vehicle: 'GADE_2', eligible: 3, classificadas: 1, completo: false, fechado: false, arquivado: false },
|
||||||
|
{ vehicle: '(sem veículo)', eligible: 0, classificadas: 0, completo: false, fechado: false, arquivado: false }
|
||||||
|
])
|
||||||
|
|
||||||
|
arquivados = consolidacao.arquivar_veiculos_pendentes!(gerente)
|
||||||
|
|
||||||
|
expect(arquivados).to eq(['A · GADE_2'])
|
||||||
|
expect(consolidacao.consolidacao_veiculos.find_by(motorista_nome: 'A', vehicle: 'GADE_2')).to be_arquivado
|
||||||
|
# não mexe no consolidado (GADE_1) nem no bucket sem elegíveis
|
||||||
|
expect(consolidacao.consolidacao_veiculos.where(vehicle: 'GADE_1')).not_to exist
|
||||||
|
expect(consolidacao.consolidacao_veiculos.where(vehicle: '(sem veículo)')).not_to exist
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe '#finalizar! fecha os veículos ainda abertos' do
|
describe '#finalizar! fecha os veículos ainda abertos' do
|
||||||
let(:consolidacao) { create(:consolidacao) }
|
let(:consolidacao) { create(:consolidacao) }
|
||||||
let(:gerente) { create(:gerente) }
|
let(:gerente) { create(:gerente) }
|
||||||
|
|||||||
Reference in New Issue
Block a user