Compare commits
4 Commits
e89122c6dd
...
c22d1da51f
| Author | SHA256 | Date | |
|---|---|---|---|
| c22d1da51f | |||
| 19b7cc838f | |||
| 13b0161dd5 | |||
| ffcae12d0b |
@@ -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.
|
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
|
## Architecture
|
||||||
|
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
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: 161 KiB |
BIN
Imagens para implantação /Imagem colada (2).png
Normal file
BIN
Imagens para implantação /Imagem colada (2).png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
36
README.md
36
README.md
@@ -217,7 +217,7 @@ config/routes.rb # ATUALIZADO — rotas do wi
|
|||||||
🟧 Laranja = Entrega Normal · 🟫 Laranja escuro = Retirada · ⬜ Branco = Bônus · ⬛ Preto = Desconto
|
🟧 Laranja = Entrega Normal · 🟫 Laranja escuro = Retirada · ⬜ Branco = Bônus · ⬛ Preto = Desconto
|
||||||
|
|
||||||
**Regras implementadas:**
|
**Regras implementadas:**
|
||||||
- Entregas elegíveis: `status='completed'` AND `checkin IS NOT NULL` (somente leitura da tabela existente)
|
- Entregas elegíveis: `status='completed'` AND `checkout IS NOT NULL` (somente leitura da tabela existente)
|
||||||
- Não permite finalizar consolidação com entregas não classificadas (botão desabilitado + validação server-side)
|
- Não permite finalizar consolidação com entregas não classificadas (botão desabilitado + validação server-side)
|
||||||
- Consolidação finalizada não pode ser editada por operadores (Pundit)
|
- Consolidação finalizada não pode ser editada por operadores (Pundit)
|
||||||
- Todas ações críticas registradas em `auditoria_logs`
|
- Todas ações críticas registradas em `auditoria_logs`
|
||||||
@@ -357,6 +357,31 @@ docker-compose exec app bundle exec rspec
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
## 🔧 Correções + funcionalidades (16/06/2026)
|
||||||
|
|
||||||
|
### 🔴 Erros de runtime corrigidos (reportados em prints)
|
||||||
|
- **Logout** dava `No route matches [GET] /auth/logout` — o link usava `method: :delete` (rails-ujs), ignorado pelo Turbo. Agora usa `data-turbo-method`.
|
||||||
|
- **/admin/usuarios** e **/admin/configuracoes** estouravam `NoMethodError: admin_ou_gerente?` — helpers de papel (`admin?`, `admin_ou_gerente?`) movidos para a `ApplicationPolicy`.
|
||||||
|
- **Listagem de usuários** chamava o helper inexistente `badge_role` — criado em `ApplicationHelper`.
|
||||||
|
- **Nova consolidação** estourava `NoMethodError: new?` — `ApplicationPolicy` agora define `new? = create?` e `edit? = update?`.
|
||||||
|
- **Login do motorista (PIN)** derrubava `/motorista` com `PG::UndefinedTable: relation "consolidacao_motorista" does not exist` — o inflector pt-BR resolvia o composto no singular. `self.table_name` fixado explicitamente em `Consolidacao`, `ConsolidacaoMotorista`, `ConsolidacaoEntrega` e `Configuracao`.
|
||||||
|
|
||||||
|
### 🆕 Funcionalidades
|
||||||
|
- **Dashboard com filtro de faixa de datas** (Flatpickr, modo range) no lugar da navegação por mês; o controller passou a consultar por período e a respeitar `.da_conta_gade`.
|
||||||
|
- **Regra de pagamento por checkout:** `Entrega.pagas` agora é `status='completed' AND checkout IS NOT NULL` (antes era `checkin`). Vale para dashboard, painel do motorista, job de 1h e elegibilidade das consolidações.
|
||||||
|
- **Consolidação por veículo:** o filtro/identificação passou de rota (`route_id`, UUID) para **veículo** (`vehicle`). Migration troca `consolidacoes.route_ids` por `vehicle_ids`.
|
||||||
|
|
||||||
|
### ⚙️ Automação / diagnóstico
|
||||||
|
- `docker-compose.yml`: o boot roda `rails db:prepare` antes do servidor — **não precisa mais rodar `db:migrate` na mão** após o `up`.
|
||||||
|
- `rake reem:diagnostico`: checa a tabela externa por conta/período/status/checkout e o intervalo de datas disponível (útil para depurar dashboard vazio).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose exec app bundle exec rake reem:diagnostico
|
||||||
|
docker-compose exec app bundle exec rake reem:diagnostico INICIO=2026-02-01 FIM=2026-06-30
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
## 🗄️ Banco existente — referência rápida
|
## 🗄️ Banco existente — referência rápida
|
||||||
|
|
||||||
| Campo | Uso |
|
| Campo | Uso |
|
||||||
@@ -365,12 +390,13 @@ docker-compose exec app bundle exec rspec
|
|||||||
| `reference_id` | Número da NF |
|
| `reference_id` | Número da NF |
|
||||||
| `planned_date` | Data da entrega (filtro principal) |
|
| `planned_date` | Data da entrega (filtro principal) |
|
||||||
| `driver` | Nome do motorista |
|
| `driver` | Nome do motorista |
|
||||||
| `status` | `'completed'` = entrega paga |
|
| `status` | `'completed'` = entrega concluída |
|
||||||
| `checkin` | NULL = motorista não registrou chegada |
|
| `checkout` | NULL = motorista não finalizou (define elegibilidade de pagamento) |
|
||||||
| `route_id` | UUID da operação (UBS SUL, EMAD, etc.) |
|
| `vehicle` | Veículo da entrega (usado no filtro de consolidação) |
|
||||||
|
| `route_id` | UUID da operação (não usado no app — substituído por `vehicle`) |
|
||||||
| `contact_name` | Nome do local (UBS SACOMA, EMAD VELEIROS…) |
|
| `contact_name` | Nome do local (UBS SACOMA, EMAD VELEIROS…) |
|
||||||
|
|
||||||
**Regra de pagamento:** `status = 'completed'` AND `checkin IS NOT NULL`
|
**Regra de pagamento:** `status = 'completed'` AND `checkout IS NOT NULL`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ class ConsolidacaoEntregasController < ApplicationController
|
|||||||
.da_conta_gade
|
.da_conta_gade
|
||||||
.no_periodo(@consolidacao.data_inicio, @consolidacao.data_fim)
|
.no_periodo(@consolidacao.data_inicio, @consolidacao.data_fim)
|
||||||
.do_motorista(@motorista)
|
.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)
|
@entregas = @entregas.order(:planned_date)
|
||||||
|
|
||||||
# Classificações já feitas — indexadas por tracking_id
|
# Classificações já feitas — indexadas por tracking_id
|
||||||
@@ -139,18 +139,18 @@ class ConsolidacaoEntregasController < ApplicationController
|
|||||||
nome.present? && @consolidacao.consolidacao_motoristas.exists?(motorista_nome: nome)
|
nome.present? && @consolidacao.consolidacao_motoristas.exists?(motorista_nome: nome)
|
||||||
end
|
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)
|
def tracking_ids_elegiveis(motorista)
|
||||||
return [] unless motorista.present?
|
return [] unless motorista.present?
|
||||||
|
|
||||||
rel = Entrega.pagas.da_conta_gade
|
rel = Entrega.pagas.da_conta_gade
|
||||||
.no_periodo(@consolidacao.data_inicio, @consolidacao.data_fim)
|
.no_periodo(@consolidacao.data_inicio, @consolidacao.data_fim)
|
||||||
.do_motorista(motorista)
|
.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)
|
rel.pluck(:tracking_id)
|
||||||
end
|
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.
|
# consolidação — impede classificar tracking_ids arbitrários via requisição.
|
||||||
def entrega_elegivel?(motorista, tracking_id)
|
def entrega_elegivel?(motorista, tracking_id)
|
||||||
motorista_na_consolidacao?(motorista) &&
|
motorista_na_consolidacao?(motorista) &&
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ class ConsolidacoesController < ApplicationController
|
|||||||
@consolidacoes = @consolidacoes.where(id: ids)
|
@consolidacoes = @consolidacoes.where(id: ids)
|
||||||
end
|
end
|
||||||
|
|
||||||
if params[:route_id].present?
|
if params[:vehicle].present?
|
||||||
@consolidacoes = @consolidacoes.where('route_ids @> ?', [params[:route_id]].to_json)
|
@consolidacoes = @consolidacoes.where('vehicle_ids @> ?', [params[:vehicle]].to_json)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ class ConsolidacoesController < ApplicationController
|
|||||||
data_fim: Date.current
|
data_fim: Date.current
|
||||||
)
|
)
|
||||||
@motoristas = Entrega.motoristas_ativos
|
@motoristas = Entrega.motoristas_ativos
|
||||||
@rotas = Entrega.rotas_unicas
|
@veiculos = Entrega.veiculos_unicos
|
||||||
end
|
end
|
||||||
|
|
||||||
# POST /consolidacoes
|
# POST /consolidacoes
|
||||||
@@ -160,10 +160,10 @@ class ConsolidacoesController < ApplicationController
|
|||||||
|
|
||||||
def preparar_form_new
|
def preparar_form_new
|
||||||
@motoristas = Entrega.motoristas_ativos
|
@motoristas = Entrega.motoristas_ativos
|
||||||
@rotas = Entrega.rotas_unicas
|
@veiculos = Entrega.veiculos_unicos
|
||||||
end
|
end
|
||||||
|
|
||||||
def consolidacao_params
|
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
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ class DashboardController < ApplicationController
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Por operação (route_id → contact_name)
|
# Por local de entrega (contact_name)
|
||||||
@por_operacao = entregas.pagas
|
@por_operacao = entregas.pagas
|
||||||
.group(:contact_name)
|
.group(:contact_name)
|
||||||
.count
|
.count
|
||||||
|
|||||||
@@ -29,6 +29,20 @@ module ApplicationHelper
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Badge de papel do usuário (admin/gerente/operador/motorista)
|
||||||
|
def badge_role(role)
|
||||||
|
cfg = case role.to_s
|
||||||
|
when 'admin' then { cor: 'bg-orange-500 text-black' }
|
||||||
|
when 'gerente' then { cor: 'bg-orange-800 text-white' }
|
||||||
|
when 'operador' then { cor: 'bg-gray-700 text-white' }
|
||||||
|
when 'motorista' then { cor: 'bg-gray-900 text-orange-400 border border-orange-500' }
|
||||||
|
else { cor: 'bg-gray-800 text-gray-400' }
|
||||||
|
end
|
||||||
|
label = User::ROLES_LABEL[role.to_s] || role.to_s.humanize
|
||||||
|
|
||||||
|
tag.span label, class: "inline-flex items-center px-2.5 py-1 rounded-full text-xs font-bold #{cfg[:cor]}"
|
||||||
|
end
|
||||||
|
|
||||||
# Barra de progresso laranja
|
# Barra de progresso laranja
|
||||||
def progress_bar(percentual, label: nil)
|
def progress_bar(percentual, label: nil)
|
||||||
pct = percentual.to_i.clamp(0, 100)
|
pct = percentual.to_i.clamp(0, 100)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# app/jobs/atualizar_historico_estimado_job.rb
|
# app/jobs/atualizar_historico_estimado_job.rb
|
||||||
#
|
#
|
||||||
# Roda a cada 1 hora (via Whenever → rake historico:atualizar).
|
# 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.
|
# multiplica pelo preco_entrega configurado e persiste em historico_estimados.
|
||||||
#
|
#
|
||||||
class AtualizarHistoricoEstimadoJob < ApplicationJob
|
class AtualizarHistoricoEstimadoJob < ApplicationJob
|
||||||
|
|||||||
@@ -73,14 +73,14 @@ class Consolidacao < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
# Entregas pagas elegíveis para um motorista no período, respeitando o filtro
|
# 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.
|
# pela revisão e pelo gate de finalização.
|
||||||
def entregas_elegiveis_count(motorista)
|
def entregas_elegiveis_count(motorista)
|
||||||
Entrega.contar_pagas(
|
Entrega.contar_pagas(
|
||||||
inicio: data_inicio,
|
inicio: data_inicio,
|
||||||
fim: data_fim,
|
fim: data_fim,
|
||||||
motorista: motorista,
|
motorista: motorista,
|
||||||
route_id: route_ids.presence
|
vehicle: vehicle_ids.presence
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -15,8 +15,9 @@ class Entrega < ApplicationRecord
|
|||||||
|
|
||||||
# ── Scopes ──────────────────────────────────────────────────
|
# ── Scopes ──────────────────────────────────────────────────
|
||||||
scope :concluidas, -> { where(status: 'completed') }
|
scope :concluidas, -> { where(status: 'completed') }
|
||||||
scope :com_checkin, -> { where.not(checkin: nil) }
|
scope :com_checkout, -> { where.not(checkout: nil) }
|
||||||
scope :pagas, -> { concluidas.com_checkin }
|
# Pagamento = entrega concluída com checkout registrado
|
||||||
|
scope :pagas, -> { concluidas.com_checkout }
|
||||||
scope :pendentes, -> { where.not(status: 'completed') }
|
scope :pendentes, -> { where.not(status: 'completed') }
|
||||||
|
|
||||||
scope :no_periodo, ->(inicio, fim) {
|
scope :no_periodo, ->(inicio, fim) {
|
||||||
@@ -27,8 +28,8 @@ class Entrega < ApplicationRecord
|
|||||||
where(driver: nome)
|
where(driver: nome)
|
||||||
}
|
}
|
||||||
|
|
||||||
scope :da_rota, ->(route_id) {
|
scope :da_veiculo, ->(vehicle) {
|
||||||
where(route_id: route_id)
|
where(vehicle: vehicle)
|
||||||
}
|
}
|
||||||
|
|
||||||
scope :do_mes, ->(data = Date.current) {
|
scope :do_mes, ->(data = Date.current) {
|
||||||
@@ -48,18 +49,18 @@ class Entrega < ApplicationRecord
|
|||||||
base.distinct.order(:driver).pluck(:driver).compact.reject(&:empty?)
|
base.distinct.order(:driver).pluck(:driver).compact.reject(&:empty?)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Lista rotas únicas (route_id → descrição)
|
# Lista veículos únicos (para selects/filtros de consolidação)
|
||||||
def self.rotas_unicas(inicio: nil, fim: nil)
|
def self.veiculos_unicos(inicio: nil, fim: nil)
|
||||||
base = da_conta_gade
|
base = da_conta_gade
|
||||||
base = base.no_periodo(inicio, fim) if inicio && fim
|
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
|
end
|
||||||
|
|
||||||
# Contagem de entregas pagas para cálculo estimado
|
# 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 = pagas.da_conta_gade.no_periodo(inicio, fim)
|
||||||
base = base.do_motorista(motorista) if motorista.present?
|
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
|
base.count
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -77,12 +78,12 @@ class Entrega < ApplicationRecord
|
|||||||
status == 'completed'
|
status == 'completed'
|
||||||
end
|
end
|
||||||
|
|
||||||
def checkin_registrado?
|
def checkout_registrado?
|
||||||
checkin.present?
|
checkout.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def elegivel_pagamento?
|
def elegivel_pagamento?
|
||||||
concluida? && checkin_registrado?
|
concluida? && checkout_registrado?
|
||||||
end
|
end
|
||||||
|
|
||||||
def atraso_minutos
|
def atraso_minutos
|
||||||
|
|||||||
@@ -15,6 +15,15 @@ class ApplicationPolicy
|
|||||||
def edit? = update?
|
def edit? = update?
|
||||||
def destroy? = user.admin?
|
def destroy? = user.admin?
|
||||||
|
|
||||||
|
# Helpers de papel disponíveis para todas as policies (delegam para o User)
|
||||||
|
def admin?
|
||||||
|
user.admin?
|
||||||
|
end
|
||||||
|
|
||||||
|
def admin_ou_gerente?
|
||||||
|
user.admin? || user.gerente?
|
||||||
|
end
|
||||||
|
|
||||||
class Scope
|
class Scope
|
||||||
def initialize(user, scope)
|
def initialize(user, scope)
|
||||||
@user = user
|
@user = user
|
||||||
|
|||||||
@@ -32,18 +32,7 @@ class UserPolicy < ApplicationPolicy
|
|||||||
admin?
|
admin?
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
# admin? e admin_ou_gerente? vêm da ApplicationPolicy.
|
||||||
|
|
||||||
# Helpers de papel usados pelas permissões acima (delegam para o User)
|
|
||||||
def admin?
|
|
||||||
user.admin?
|
|
||||||
end
|
|
||||||
|
|
||||||
def admin_ou_gerente?
|
|
||||||
user.admin? || user.gerente?
|
|
||||||
end
|
|
||||||
|
|
||||||
public
|
|
||||||
|
|
||||||
class Scope < Scope
|
class Scope < Scope
|
||||||
def resolve
|
def resolve
|
||||||
|
|||||||
@@ -85,7 +85,7 @@
|
|||||||
NF <%= e.numero_nf %> · <%= e.local %>
|
NF <%= e.numero_nf %> · <%= e.local %>
|
||||||
</p>
|
</p>
|
||||||
<p class="text-gray-500 text-xs">
|
<p class="text-gray-500 text-xs">
|
||||||
📅 <%= 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 %>
|
<% if e.atraso_minutos > 0 %> · ⏱️ atraso <%= e.atraso_minutos %>min<% end %>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -63,16 +63,19 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<%# Rotas — multi-select opcional %>
|
<%# Veículos — multi-select opcional %>
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-white font-semibold mb-2">
|
<label class="block text-white font-semibold mb-2">
|
||||||
Rotas <span class="text-gray-500 font-normal text-sm">(opcional — filtra entregas por operação)</span>
|
Veículos <span class="text-gray-500 font-normal text-sm">(opcional — filtra entregas por veículo)</span>
|
||||||
</label>
|
</label>
|
||||||
<div class="bg-[#0a0a0a] border border-[#2a2a2a] rounded-lg p-3 max-h-48 overflow-y-auto space-y-1">
|
<div class="bg-[#0a0a0a] border border-[#2a2a2a] rounded-lg p-3 max-h-48 overflow-y-auto space-y-1">
|
||||||
<% @rotas.each do |r| %>
|
<% if @veiculos.blank? %>
|
||||||
<label class="flex items-center gap-2 text-white hover:bg-[#1a1a1a] rounded px-2 py-2 cursor-pointer text-sm font-mono min-h-[44px]">
|
<p class="text-gray-500 text-sm px-2 py-2">Nenhum veículo encontrado no período.</p>
|
||||||
<%= check_box_tag 'consolidacao[route_ids][]', r, false, class: 'rounded text-orange-500 bg-[#1a1a1a] border-[#2a2a2a]', id: nil %>
|
<% end %>
|
||||||
<%= r %>
|
<% @veiculos.each do |v| %>
|
||||||
|
<label class="flex items-center gap-2 text-white hover:bg-[#1a1a1a] rounded px-2 py-2 cursor-pointer text-sm min-h-[44px]">
|
||||||
|
<%= check_box_tag 'consolidacao[vehicle_ids][]', v, false, class: 'rounded text-orange-500 bg-[#1a1a1a] border-[#2a2a2a]', id: nil %>
|
||||||
|
<%= v %>
|
||||||
</label>
|
</label>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -15,12 +15,24 @@ namespace :reem do
|
|||||||
puts "account_ids presentes na tabela:"
|
puts "account_ids presentes na tabela:"
|
||||||
Entrega.group(:account_id).count.each { |acc, qtd| puts " account_id=#{acc.inspect} → #{qtd} linhas" }
|
Entrega.group(:account_id).count.each { |acc, qtd| puts " account_id=#{acc.inspect} → #{qtd} linhas" }
|
||||||
puts "-------------------------------------------------"
|
puts "-------------------------------------------------"
|
||||||
|
puts "status presentes na tabela:"
|
||||||
|
Entrega.group(:status).count.each { |st, qtd| puts " status=#{st.inspect} → #{qtd} linhas" }
|
||||||
|
puts "-------------------------------------------------"
|
||||||
|
no_periodo = Entrega.no_periodo(ini, fim)
|
||||||
|
puts "No período (sem filtro de conta):"
|
||||||
|
puts " total: #{no_periodo.count}"
|
||||||
|
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 + checkout): #{no_periodo.pagas.count}"
|
||||||
|
puts "-------------------------------------------------"
|
||||||
puts "planned_date mais antiga: #{Entrega.minimum(:planned_date)}"
|
puts "planned_date mais antiga: #{Entrega.minimum(:planned_date)}"
|
||||||
puts "planned_date mais recente: #{Entrega.maximum(:planned_date)}"
|
puts "planned_date mais recente: #{Entrega.maximum(:planned_date)}"
|
||||||
puts "================================================="
|
puts "================================================="
|
||||||
puts "Leitura:"
|
puts "Leitura:"
|
||||||
puts " • 'SEM filtro' > 0 e 'com filtro Gade' = 0 → ajuste DB_EXISTING_ACCOUNT_ID no .env"
|
puts " • 'SEM filtro' > 0 e 'com filtro Gade' = 0 → ajuste DB_EXISTING_ACCOUNT_ID no .env"
|
||||||
puts " • ambos = 0 → não há dados nesse período (use as datas acima)"
|
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
|
rescue => e
|
||||||
warn "[diagnostico] Falha: #{e.class} - #{e.message}"
|
warn "[diagnostico] Falha: #{e.class} - #{e.message}"
|
||||||
raise
|
raise
|
||||||
|
|||||||
20
spec/policies/configuracao_policy_spec.rb
Normal file
20
spec/policies/configuracao_policy_spec.rb
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe ConfiguracaoPolicy do
|
||||||
|
subject { described_class }
|
||||||
|
|
||||||
|
permissions :index? do
|
||||||
|
it 'libera para admin e gerente, nega operador' do
|
||||||
|
expect(subject).to permit(build(:admin), Configuracao)
|
||||||
|
expect(subject).to permit(build(:gerente), Configuracao)
|
||||||
|
expect(subject).not_to permit(build(:operador), Configuracao)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
permissions :edit?, :update? do
|
||||||
|
it 'só admin altera configurações' do
|
||||||
|
expect(subject).to permit(build(:admin), Configuracao)
|
||||||
|
expect(subject).not_to permit(build(:gerente), Configuracao)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
Reference in New Issue
Block a user