From 19b7cc838f8c0b225300746a5e45d172a45239f3fed6286001198fba7c016e05 Mon Sep 17 00:00:00 2001 From: victor Date: Tue, 16 Jun 2026 02:17:25 -0300 Subject: [PATCH] =?UTF-8?q?Pagamento=20por=20checkout=20+=20consolida?= =?UTF-8?q?=C3=A7=C3=A3o=20por=20ve=C3=ADculo=20(em=20vez=20de=20rota)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Regra de pagamento: - Entrega.pagas agora exige status='completed' E checkout preenchido (antes era checkin). Afeta dashboard, painel do motorista, job 1h e elegibilidade das consolidações de forma consistente. Veículo no lugar de rota na consolidação: - migration troca consolidacoes.route_ids por vehicle_ids (jsonb + gin) - Entrega: da_veiculo/veiculos_unicos; contar_pagas usa vehicle - new.html.erb: filtro "Veículos" (multi-select por vehicle) - validar.html.erb: mostra o veículo em vez do código da rota - filtros/elegibilidade do controller passam a usar vehicle_ids Docs/diagnóstico atualizados. Co-Authored-By: Claude Opus 4.8 --- CLAUDE.md | 2 +- .../consolidacao_entregas_controller.rb | 8 +++--- app/controllers/consolidacoes_controller.rb | 10 ++++---- app/controllers/dashboard_controller.rb | 2 +- app/jobs/atualizar_historico_estimado_job.rb | 2 +- app/models/consolidacao.rb | 4 +-- app/models/entrega.rb | 25 ++++++++++--------- .../consolidacao_entregas/validar.html.erb | 2 +- app/views/consolidacoes/new.html.erb | 15 ++++++----- ...te_ids_for_vehicle_ids_on_consolidacoes.rb | 19 ++++++++++++++ lib/tasks/diagnostico.rake | 4 +-- 11 files changed, 58 insertions(+), 35 deletions(-) create mode 100644 db/migrate/20260616000001_swap_route_ids_for_vehicle_ids_on_consolidacoes.rb diff --git a/CLAUDE.md b/CLAUDE.md index de05f33..d8621e6 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -54,7 +54,7 @@ Default credentials (change in production): `admin@gade.com` / `Gade@2026!` The table `public.db_reem_simplerout_2026` is owned by an external system (SimpleRoute) that updates it every hour. **NEVER** create migrations for it, and never run INSERT/UPDATE/DELETE/DROP/TRUNCATE on it. -`Entrega` (app/models/entrega.rb) maps to this table and enforces `readonly? = true`. All queries go through its scopes — the most important being `.pagas` (`status='completed' AND checkin IS NOT NULL`), which defines payment eligibility. +`Entrega` (app/models/entrega.rb) maps to this table and enforces `readonly? = true`. All queries go through its scopes — the most important being `.pagas` (`status='completed' AND checkout IS NOT NULL`), which defines payment eligibility. Consolidations filter eligible deliveries by **vehicle** (`vehicle` column → `Consolidacao#vehicle_ids`), not by route. ## Architecture diff --git a/app/controllers/consolidacao_entregas_controller.rb b/app/controllers/consolidacao_entregas_controller.rb index fd0d1de..069f53b 100644 --- a/app/controllers/consolidacao_entregas_controller.rb +++ b/app/controllers/consolidacao_entregas_controller.rb @@ -16,7 +16,7 @@ class ConsolidacaoEntregasController < ApplicationController .da_conta_gade .no_periodo(@consolidacao.data_inicio, @consolidacao.data_fim) .do_motorista(@motorista) - @entregas = @entregas.da_rota(@consolidacao.route_ids) if @consolidacao.route_ids.present? + @entregas = @entregas.da_veiculo(@consolidacao.vehicle_ids) if @consolidacao.vehicle_ids.present? @entregas = @entregas.order(:planned_date) # Classificações já feitas — indexadas por tracking_id @@ -139,18 +139,18 @@ class ConsolidacaoEntregasController < ApplicationController nome.present? && @consolidacao.consolidacao_motoristas.exists?(motorista_nome: nome) end - # tracking_ids pagos/elegíveis para o motorista no período (respeita rotas) + # tracking_ids pagos/elegíveis para o motorista no período (respeita veículos) def tracking_ids_elegiveis(motorista) return [] unless motorista.present? rel = Entrega.pagas.da_conta_gade .no_periodo(@consolidacao.data_inicio, @consolidacao.data_fim) .do_motorista(motorista) - rel = rel.da_rota(@consolidacao.route_ids) if @consolidacao.route_ids.present? + rel = rel.da_veiculo(@consolidacao.vehicle_ids) if @consolidacao.vehicle_ids.present? rel.pluck(:tracking_id) end - # Valida que a entrega pertence ao motorista informado e ao período/rota da + # Valida que a entrega pertence ao motorista informado e ao período/veículo da # consolidação — impede classificar tracking_ids arbitrários via requisição. def entrega_elegivel?(motorista, tracking_id) motorista_na_consolidacao?(motorista) && diff --git a/app/controllers/consolidacoes_controller.rb b/app/controllers/consolidacoes_controller.rb index b160e0c..5571c03 100644 --- a/app/controllers/consolidacoes_controller.rb +++ b/app/controllers/consolidacoes_controller.rb @@ -22,8 +22,8 @@ class ConsolidacoesController < ApplicationController @consolidacoes = @consolidacoes.where(id: ids) end - if params[:route_id].present? - @consolidacoes = @consolidacoes.where('route_ids @> ?', [params[:route_id]].to_json) + if params[:vehicle].present? + @consolidacoes = @consolidacoes.where('vehicle_ids @> ?', [params[:vehicle]].to_json) end end @@ -35,7 +35,7 @@ class ConsolidacoesController < ApplicationController data_fim: Date.current ) @motoristas = Entrega.motoristas_ativos - @rotas = Entrega.rotas_unicas + @veiculos = Entrega.veiculos_unicos end # POST /consolidacoes @@ -160,10 +160,10 @@ class ConsolidacoesController < ApplicationController def preparar_form_new @motoristas = Entrega.motoristas_ativos - @rotas = Entrega.rotas_unicas + @veiculos = Entrega.veiculos_unicos end def consolidacao_params - params.require(:consolidacao).permit(:nome, :data_inicio, :data_fim, route_ids: []) + params.require(:consolidacao).permit(:nome, :data_inicio, :data_fim, vehicle_ids: []) end end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 72ee361..536e8e5 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -61,7 +61,7 @@ class DashboardController < ApplicationController } end - # Por operação (route_id → contact_name) + # Por local de entrega (contact_name) @por_operacao = entregas.pagas .group(:contact_name) .count diff --git a/app/jobs/atualizar_historico_estimado_job.rb b/app/jobs/atualizar_historico_estimado_job.rb index 4a6708d..841d150 100644 --- a/app/jobs/atualizar_historico_estimado_job.rb +++ b/app/jobs/atualizar_historico_estimado_job.rb @@ -1,7 +1,7 @@ # app/jobs/atualizar_historico_estimado_job.rb # # Roda a cada 1 hora (via Whenever → rake historico:atualizar). -# Lógica: conta entregas do dia com status='completed' E checkin IS NOT NULL, +# Lógica: conta entregas do dia com status='completed' E checkout IS NOT NULL, # multiplica pelo preco_entrega configurado e persiste em historico_estimados. # class AtualizarHistoricoEstimadoJob < ApplicationJob diff --git a/app/models/consolidacao.rb b/app/models/consolidacao.rb index 089fc35..a0bebba 100644 --- a/app/models/consolidacao.rb +++ b/app/models/consolidacao.rb @@ -73,14 +73,14 @@ class Consolidacao < ApplicationRecord end # Entregas pagas elegíveis para um motorista no período, respeitando o filtro - # de rotas da consolidação (quando houver). Fonte única usada pelo wizard, + # de veículos da consolidação (quando houver). Fonte única usada pelo wizard, # pela revisão e pelo gate de finalização. def entregas_elegiveis_count(motorista) Entrega.contar_pagas( inicio: data_inicio, fim: data_fim, motorista: motorista, - route_id: route_ids.presence + vehicle: vehicle_ids.presence ) end diff --git a/app/models/entrega.rb b/app/models/entrega.rb index fd6f5f0..f384028 100644 --- a/app/models/entrega.rb +++ b/app/models/entrega.rb @@ -15,8 +15,9 @@ class Entrega < ApplicationRecord # ── Scopes ────────────────────────────────────────────────── scope :concluidas, -> { where(status: 'completed') } - scope :com_checkin, -> { where.not(checkin: nil) } - scope :pagas, -> { concluidas.com_checkin } + scope :com_checkout, -> { where.not(checkout: nil) } + # Pagamento = entrega concluída com checkout registrado + scope :pagas, -> { concluidas.com_checkout } scope :pendentes, -> { where.not(status: 'completed') } scope :no_periodo, ->(inicio, fim) { @@ -27,8 +28,8 @@ class Entrega < ApplicationRecord where(driver: nome) } - scope :da_rota, ->(route_id) { - where(route_id: route_id) + scope :da_veiculo, ->(vehicle) { + where(vehicle: vehicle) } scope :do_mes, ->(data = Date.current) { @@ -48,18 +49,18 @@ class Entrega < ApplicationRecord base.distinct.order(:driver).pluck(:driver).compact.reject(&:empty?) end - # Lista rotas únicas (route_id → descrição) - def self.rotas_unicas(inicio: nil, fim: nil) + # Lista veículos únicos (para selects/filtros de consolidação) + def self.veiculos_unicos(inicio: nil, fim: nil) base = da_conta_gade base = base.no_periodo(inicio, fim) if inicio && fim - base.distinct.pluck(:route_id).compact + base.distinct.order(:vehicle).pluck(:vehicle).compact.reject(&:empty?) end # Contagem de entregas pagas para cálculo estimado - def self.contar_pagas(inicio:, fim:, motorista: nil, route_id: nil) + def self.contar_pagas(inicio:, fim:, motorista: nil, vehicle: nil) base = pagas.da_conta_gade.no_periodo(inicio, fim) base = base.do_motorista(motorista) if motorista.present? - base = base.da_rota(route_id) if route_id.present? + base = base.da_veiculo(vehicle) if vehicle.present? base.count end @@ -77,12 +78,12 @@ class Entrega < ApplicationRecord status == 'completed' end - def checkin_registrado? - checkin.present? + def checkout_registrado? + checkout.present? end def elegivel_pagamento? - concluida? && checkin_registrado? + concluida? && checkout_registrado? end def atraso_minutos diff --git a/app/views/consolidacao_entregas/validar.html.erb b/app/views/consolidacao_entregas/validar.html.erb index 33ea077..f4dba69 100644 --- a/app/views/consolidacao_entregas/validar.html.erb +++ b/app/views/consolidacao_entregas/validar.html.erb @@ -85,7 +85,7 @@ NF <%= e.numero_nf %> · <%= e.local %>

- 📅 <%= e.planned_date %> · 📍 <%= e.route_id.to_s.first(8) %>… + 📅 <%= e.planned_date %> · 🚚 <%= e.vehicle.presence || 'sem veículo' %> <% if e.atraso_minutos > 0 %> · ⏱️ atraso <%= e.atraso_minutos %>min<% end %>

diff --git a/app/views/consolidacoes/new.html.erb b/app/views/consolidacoes/new.html.erb index 8017fc5..461727d 100644 --- a/app/views/consolidacoes/new.html.erb +++ b/app/views/consolidacoes/new.html.erb @@ -63,16 +63,19 @@ - <%# Rotas — multi-select opcional %> + <%# Veículos — multi-select opcional %>
- <% @rotas.each do |r| %> -
diff --git a/db/migrate/20260616000001_swap_route_ids_for_vehicle_ids_on_consolidacoes.rb b/db/migrate/20260616000001_swap_route_ids_for_vehicle_ids_on_consolidacoes.rb new file mode 100644 index 0000000..af5f92a --- /dev/null +++ b/db/migrate/20260616000001_swap_route_ids_for_vehicle_ids_on_consolidacoes.rb @@ -0,0 +1,19 @@ +# Consolidação passa a filtrar por VEÍCULO em vez de rota (route_id era um +# UUID pouco legível). Troca a coluna route_ids por vehicle_ids. +class SwapRouteIdsForVehicleIdsOnConsolidacoes < ActiveRecord::Migration[7.1] + def up + unless column_exists?(:consolidacoes, :vehicle_ids) + add_column :consolidacoes, :vehicle_ids, :jsonb, default: [] + add_index :consolidacoes, :vehicle_ids, using: :gin + end + remove_column :consolidacoes, :route_ids if column_exists?(:consolidacoes, :route_ids) + end + + def down + unless column_exists?(:consolidacoes, :route_ids) + add_column :consolidacoes, :route_ids, :jsonb, default: [] + add_index :consolidacoes, :route_ids, using: :gin + end + remove_column :consolidacoes, :vehicle_ids if column_exists?(:consolidacoes, :vehicle_ids) + end +end diff --git a/lib/tasks/diagnostico.rake b/lib/tasks/diagnostico.rake index 760d1bc..963289a 100644 --- a/lib/tasks/diagnostico.rake +++ b/lib/tasks/diagnostico.rake @@ -24,14 +24,14 @@ namespace :reem do puts " status='completed': #{no_periodo.where(status: 'completed').count}" puts " checkin preenchido: #{no_periodo.where.not(checkin: nil).count}" puts " checkout preenchido: #{no_periodo.where.not(checkout: nil).count}" - puts " .pagas (completed + checkin): #{no_periodo.pagas.count}" + puts " .pagas (completed + checkout): #{no_periodo.pagas.count}" puts "-------------------------------------------------" puts "planned_date mais antiga: #{Entrega.minimum(:planned_date)}" puts "planned_date mais recente: #{Entrega.maximum(:planned_date)}" puts "=================================================" puts "Leitura:" puts " • 'SEM filtro' > 0 e 'com filtro Gade' = 0 → ajuste DB_EXISTING_ACCOUNT_ID no .env" - puts " • total > 0 mas '.pagas' = 0 (checkin vazio) → regra de pagamento deveria usar checkout" + puts " • total > 0 mas '.pagas' = 0 (checkout vazio) → entregas sem checkout não contam como pagas" puts " • tudo 0 → não há dados nesse período (veja datas acima)" rescue => e warn "[diagnostico] Falha: #{e.class} - #{e.message}"