require 'rails_helper' RSpec.describe Consolidacao, type: :model do describe 'validação de período' do it 'rejeita data_fim anterior a data_inicio' do c = build(:consolidacao, data_inicio: Date.new(2026, 6, 10), data_fim: Date.new(2026, 6, 1)) expect(c).not_to be_valid expect(c.errors[:data_fim]).to be_present end it 'aceita período válido' do expect(build(:consolidacao)).to be_valid end end describe '#recalcular_valor_total (before_save)' do it 'soma os valores dos motoristas da consolidação' do consolidacao = create(:consolidacao) create(:consolidacao_motorista, consolidacao: consolidacao, motorista_nome: 'A') create(:consolidacao_motorista, consolidacao: consolidacao, motorista_nome: 'B') consolidacao.consolidacao_motoristas.update_all(valor_total: 30) consolidacao.save! expect(consolidacao.reload.valor_total).to eq(60) end end describe '#finalizar!' do let(:consolidacao) { create(:consolidacao) } let(:gerente) { create(:gerente) } it 'não finaliza quando há entregas não classificadas' do allow(consolidacao).to receive(:todas_entregas_classificadas?).and_return(false) expect(consolidacao.finalizar!(gerente)).to be_falsey expect(consolidacao.reload).to be_rascunho end it 'finaliza e registra finalizador quando tudo está classificado' do allow(consolidacao).to receive(:todas_entregas_classificadas?).and_return(true) consolidacao.finalizar!(gerente) consolidacao.reload expect(consolidacao).to be_finalizada expect(consolidacao.finalizado_por).to eq(gerente.id) expect(consolidacao.finalizado_em).to be_present end end describe '#arquivar!' do it 'marca deleted_at, status arquivada e registra quem arquivou' do gerente = create(:gerente) consolidacao = create(:consolidacao) consolidacao.arquivar!(gerente) consolidacao.reload expect(consolidacao).to be_arquivada expect(consolidacao.deleted_at).to be_present expect(consolidacao.arquivado_por).to eq(gerente.id) expect(consolidacao.arquivado_em).to be_present expect(consolidacao.arquivador).to eq(gerente) end end describe '#todas_entregas_classificadas? (gate por motorista)' do let(:consolidacao) { create(:consolidacao) } before do create(:consolidacao_motorista, consolidacao: consolidacao, motorista_nome: 'A') create(:consolidacao_motorista, consolidacao: consolidacao, motorista_nome: 'B') end it 'é falso quando UM motorista tem pendências, mesmo que outro tenha entregas de sobra' do allow(consolidacao).to receive(:entregas_elegiveis_count).and_return(2) # A: 0 classificadas | B: 4 classificadas. A soma agregada (4 >= 4) passaria, # mas a checagem por motorista deve reprovar. create_list(:consolidacao_entrega, 4, consolidacao: consolidacao, motorista_nome: 'B') expect(consolidacao.todas_entregas_classificadas?).to be false end it 'é verdadeiro quando cada motorista está completo' do allow(consolidacao).to receive(:entregas_elegiveis_count).and_return(1) create(:consolidacao_entrega, consolidacao: consolidacao, motorista_nome: 'A') create(:consolidacao_entrega, consolidacao: consolidacao, motorista_nome: 'B') expect(consolidacao.todas_entregas_classificadas?).to be true end it 'ignora motoristas sem entregas elegíveis (total zero)' do allow(consolidacao).to receive(:entregas_elegiveis_count).and_return(0) expect(consolidacao.todas_entregas_classificadas?).to be true end end describe '#classificadas_count (ignora apontamentos manuais)' do let(:consolidacao) { create(:consolidacao) } it 'conta entregas elegíveis e ignora as manuais (não infla o progresso)' do create(:consolidacao_entrega, consolidacao: consolidacao, motorista_nome: 'A', tracking_id: 'T1') create(:consolidacao_entrega, consolidacao: consolidacao, motorista_nome: 'A', tracking_id: 'T2') # Apontamento manual — fora do conjunto elegível do período. create(:consolidacao_entrega, consolidacao: consolidacao, motorista_nome: 'A', tracking_id: 'T3', manual: true) expect(consolidacao.classificadas_count('A')).to eq(2) end it 'não ultrapassa o total elegível por causa de apontamentos (gate continua válido)' do create(:consolidacao_motorista, consolidacao: consolidacao, motorista_nome: 'A') allow(consolidacao).to receive(:entregas_elegiveis_count).and_return(1) create(:consolidacao_entrega, consolidacao: consolidacao, motorista_nome: 'A', tracking_id: 'T1') create(:consolidacao_entrega, consolidacao: consolidacao, motorista_nome: 'A', tracking_id: 'T2', manual: true) expect(consolidacao.classificadas_count('A')).to eq(1) expect(consolidacao.todas_entregas_classificadas?).to be true end end describe '#adicionar_apontamento! (apontamento manual multi-tipo)' do let(:consolidacao) { create(:consolidacao) } let(:user) { create(:gerente) } let(:entrega) { Struct.new(:tracking_id, :driver).new('TRK-AV', 'Steferson Lucas') } before { allow(ConsolidacaoEntrega).to receive(:valor_para).and_return(15.0) } it 'inclui o motorista e cria um pilar manual por tipo, somando o valor' do consolidacao.adicionar_apontamento!(entrega, %w[entrega_normal bonus], user) cm = consolidacao.consolidacao_motoristas.find_by(motorista_nome: 'Steferson Lucas') expect(cm).to be_present pilares = consolidacao.consolidacao_entregas.where(tracking_id: 'TRK-AV') expect(pilares.count).to eq(2) expect(pilares.all?(&:manual?)).to be true expect(cm.reload.valor_total).to eq(30.0) end it 'é idempotente por (tracking, tipo) e exige ao menos um tipo válido' do consolidacao.adicionar_apontamento!(entrega, %w[entrega_normal], user) consolidacao.adicionar_apontamento!(entrega, %w[entrega_normal], user) expect(consolidacao.consolidacao_entregas.where(tracking_id: 'TRK-AV').count).to eq(1) expect { consolidacao.adicionar_apontamento!(entrega, [], user) }.to raise_error(ArgumentError) end end describe '#adicionar_apontamento_manual! (100% manual, sem rastreio)' do let(:consolidacao) { create(:consolidacao) } let(:user) { create(:gerente) } before { allow(ConsolidacaoEntrega).to receive(:valor_para).and_return(15.0) } it 'cria pilares manuais com nf/obs, inclui o motorista e soma o valor' do consolidacao.adicionar_apontamento_manual!( motorista: 'Motorista A', tipos: %w[entrega_normal bonus], nf: '12345', obs: 'UBS Sacomã', user: user ) cm = consolidacao.consolidacao_motoristas.find_by(motorista_nome: 'Motorista A') expect(cm).to be_present pilares = consolidacao.consolidacao_entregas.where(motorista_nome: 'Motorista A') expect(pilares.count).to eq(2) expect(pilares.all?(&:manual?)).to be true expect(pilares.map(&:nf_manual).uniq).to eq(['12345']) expect(pilares.map(&:obs_manual).uniq).to eq(['UBS Sacomã']) # Mesmo tracking_id sintético agrupa os pilares como UMA entrega. expect(pilares.map(&:tracking_id).uniq.size).to eq(1) expect(cm.reload.valor_total).to eq(30.0) end it 'não conta no gate (manual) mas exige motorista e ao menos um tipo' do allow(consolidacao).to receive(:entregas_elegiveis_count).and_return(0) consolidacao.adicionar_apontamento_manual!(motorista: 'Motorista A', tipos: %w[entrega_normal], user: user) expect(consolidacao.classificadas_count('Motorista A')).to eq(0) expect { consolidacao.adicionar_apontamento_manual!(motorista: '', tipos: %w[entrega_normal], user: user) } .to raise_error(ArgumentError) expect { consolidacao.adicionar_apontamento_manual!(motorista: 'X', tipos: [], user: user) } .to raise_error(ArgumentError) end it 'usa valor_extra para a Extraordinária quando informado' do consolidacao.adicionar_apontamento_manual!( motorista: 'B', tipos: %w[extraordinaria], valor_extra: 99.0, user: user ) pilar = consolidacao.consolidacao_entregas.find_by(motorista_nome: 'B', tipo: :extraordinaria) expect(pilar.valor_aplicado).to eq(99.0) end end describe 'arquivamento de motorista (reversível)' do let(:consolidacao) { create(:consolidacao) } let(:gerente) { create(:gerente) } before do create(:consolidacao_entrega, consolidacao: consolidacao, motorista_nome: 'A', tipo: :entrega_normal, valor_aplicado: 20) create(:consolidacao_entrega, consolidacao: consolidacao, motorista_nome: 'B', tipo: :entrega_normal, valor_aplicado: 30) @cm_a = create(:consolidacao_motorista, consolidacao: consolidacao, motorista_nome: 'A') @cm_b = create(:consolidacao_motorista, consolidacao: consolidacao, motorista_nome: 'B') end it 'tira o motorista arquivado do total e do gate, e restaura ao desarquivar' do consolidacao.update_column(:valor_total, consolidacao.consolidacao_motoristas.ativos.sum(:valor_total)) expect(consolidacao.reload.valor_total).to eq(50) @cm_a.arquivar!(gerente) consolidacao.recalcular_motorista!('A') expect(@cm_a.reload).to be_arquivado expect(consolidacao.reload.valor_total).to eq(30) # só B conta expect(ConsolidacaoMotorista.ativos).to contain_exactly(@cm_b) expect(ConsolidacaoMotorista.arquivados).to contain_exactly(@cm_a) # Gate ignora o motorista arquivado (A estava sem classificação completa). allow(consolidacao).to receive(:entregas_elegiveis_count).and_return(0) allow(consolidacao).to receive(:entregas_elegiveis_count).with('B').and_return(1) expect(consolidacao.todas_entregas_classificadas?).to be true @cm_a.desarquivar!(gerente) consolidacao.recalcular_motorista!('A') expect(consolidacao.reload.valor_total).to eq(50) end end describe 'escopos de soft delete' do it 'separa ativas de arquivadas' do ativa = create(:consolidacao) arquivada = create(:consolidacao) arquivada.arquivar!(create(:gerente)) expect(Consolidacao.ativas).to include(ativa) expect(Consolidacao.ativas).not_to include(arquivada) expect(Consolidacao.arquivadas).to include(arquivada) end end end