Files
Reem-Notas/app/models/entrega.rb
victor fedf5bfcc9 Corrige dashboard por período, CRUD de usuário, PDFs e paginação na revisão
- Dashboard: no_periodo passa a cobrir o dia inteiro (planned_date é timestamp),
  corrigindo período que zerava ao terminar no dia dos dados
- Dashboard: card de consolidações filtra por sobreposição de datas (não created_at)
- Usuários: adiciona helper role_options_for_select (corrige edição/criação)
- Consolidação: set_consolidacao nas actions de PDF/preview (corrige
  Pundit::NotDefinedError em Relatório Individual, Gerar Holerite e Preview)
- Revisão: paginação com Pagy (20/página + seletor 20/50/100)
- Nova consolidação: veículos filtrados pelo(s) motorista(s) no período
2026-06-16 14:45:06 -03:00

117 lines
4.0 KiB
Ruby

# app/models/entrega.rb
#
# Model de LEITURA para a tabela existente da Reem Transporte.
# NUNCA criar migration para esta tabela.
# NUNCA executar INSERT, UPDATE, DELETE ou DROP nesta tabela.
#
class Entrega < ApplicationRecord
self.table_name = 'db_reem_simplerout_2026'
self.primary_key = 'tracking_id'
# Apenas leitura — segurança contra mutações acidentais
def readonly?
true
end
# ── Scopes ──────────────────────────────────────────────────
scope :concluidas, -> { where(status: 'completed') }
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') }
# planned_date é timestamp: usamos o dia inteiro do limite superior para não
# descartar as entregas do último dia do período (causava dashboard zerado e
# subcontagem de elegíveis nas consolidações).
scope :no_periodo, ->(inicio, fim) {
where(planned_date: inicio.to_date.beginning_of_day..fim.to_date.end_of_day)
}
scope :do_motorista, ->(nome) {
where(driver: nome)
}
scope :da_veiculo, ->(vehicle) {
where(vehicle: vehicle)
}
scope :do_mes, ->(data = Date.current) {
where(planned_date: data.beginning_of_month..data.end_of_month)
}
# Filtra pela(s) conta(s) configurada(s) em DB_EXISTING_ACCOUNT_ID.
# A coluna account_id é TEXTO e pode vir vazia, então comparamos como string.
# Aceita: "95907" (uma) | "95907,12345" (várias) | "all" ou vazio (sem filtro).
# Use "95907," (com vírgula) para incluir também os registros de conta vazia.
scope :da_conta_gade, -> {
contas = ENV.fetch('DB_EXISTING_ACCOUNT_ID', '95907').to_s.strip
if contas.empty? || contas.casecmp?('all')
all
else
where(account_id: contas.split(',', -1).map(&:strip))
end
}
# ── Métodos de classe ────────────────────────────────────────
# Lista motoristas únicos (para selects, consolidações)
def self.motoristas_ativos(inicio: nil, fim: nil)
base = da_conta_gade
base = base.no_periodo(inicio, fim) if inicio && fim
base.distinct.order(:driver).pluck(:driver).compact.reject(&:empty?)
end
# 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.order(:vehicle).pluck(:vehicle).compact.reject(&:empty?)
end
# Veículos usados pelo(s) motorista(s) selecionado(s) no período — alimenta o
# filtro dinâmico de veículos na criação de consolidação.
def self.veiculos_de_motoristas(motoristas, inicio: nil, fim: nil)
nomes = Array(motoristas).reject(&:blank?)
return [] if nomes.empty?
base = da_conta_gade.where(driver: nomes)
base = base.no_periodo(inicio, fim) if inicio.present? && fim.present?
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, vehicle: nil)
base = pagas.da_conta_gade.no_periodo(inicio, fim)
base = base.do_motorista(motorista) if motorista.present?
base = base.da_veiculo(vehicle) if vehicle.present?
base.count
end
# ── Helpers de instância ─────────────────────────────────────
def numero_nf
reference_id
end
def local
contact_name.presence || address
end
def concluida?
status == 'completed'
end
def checkout_registrado?
checkout.present?
end
def elegivel_pagamento?
concluida? && checkout_registrado?
end
def atraso_minutos
return 0 unless delay.present?
delay.to_s.split(':').then { |h, m, _s| h.to_i * 60 + m.to_i }
end
end