# app/controllers/dashboard_controller.rb class DashboardController < ApplicationController MAX_DIAS_GRAFICO = 92 # limita a granularidade diária do gráfico (≈ 1 trimestre) def index skip_authorization # Motorista tem painel próprio — não enxerga o dashboard administrativo return redirect_to(motorista_dashboard_path) if current_user.motorista? @periodo_inicio, @periodo_fim = periodo_selecionado carregar_dados_dashboard end private # Faixa de datas vinda do filtro de calendário (params inicio/fim). # Default: do início do mês corrente até hoje. Datas inválidas caem no default # e a ordem é normalizada caso venham invertidas. def periodo_selecionado inicio = parse_data(params[:inicio]) || Date.current.beginning_of_month fim = parse_data(params[:fim]) || Date.current inicio, fim = fim, inicio if fim < inicio [inicio, fim] end # Parse tolerante: data inválida/ausente vira nil (para o default assumir). def parse_data(str) return nil if str.blank? Date.parse(str) rescue ArgumentError, TypeError nil end def carregar_dados_dashboard # Entregas do período (tabela read-only) — restritas à conta Gade entregas = Entrega.da_conta_gade.no_periodo(@periodo_inicio, @periodo_fim) # Totais gerais @total_entregas = entregas.count @entregas_pagas = entregas.pagas.count @entregas_pendentes = @total_entregas - @entregas_pagas # Configurações de preço config = Configuracao.mapa_de_precos # Valor estimado total @valor_estimado = (@entregas_pagas * config[:entrega]).round(2) # Por motorista (top 10) @motoristas = entregas.pagas .group(:driver) .count .sort_by { |_, v| -v } .first(10) .map do |driver, qtd| { nome: driver, entregas: qtd, valor: (qtd * config[:entrega]).round(2) } end # Por local de entrega (contact_name) @por_operacao = entregas.pagas .group(:contact_name) .count .sort_by { |_, v| -v } .first(6) .to_h # Evolução diária no período (para Chart.js) @grafico_diario = build_grafico_diario(entregas, config[:entrega]) # Consolidações cujo período (data_inicio..data_fim) cruza o período do dashboard # — independe de quando foram criadas (created_at). @consolidacoes_mes = Consolidacao.ativas .where('data_inicio <= ? AND data_fim >= ?', @periodo_fim, @periodo_inicio) @consolidacoes_abertas = @consolidacoes_mes.where(status: :rascunho).count @consolidacoes_fechadas = @consolidacoes_mes.where(status: :finalizada).count # Pagamentos: pago vs pendente entre as consolidações FINALIZADAS do período # (medido por motorista — fonte: consolidacao_motoristas). carregar_dados_pagamentos # Histórico estimado mais recente @historico_recente = HistoricoEstimado.order(created_at: :desc).limit(5) end def carregar_dados_pagamentos intervalo = @periodo_inicio.beginning_of_day..@periodo_fim.end_of_day # PAGOS: filtrados pela DATA DO PAGAMENTO (pago_em) dentro do período do topo # — "quanto foi pago neste período". pagos = ConsolidacaoMotorista .joins(:consolidacao) .where(consolidacoes: { status: :finalizada, deleted_at: nil }) .where(pago_em: intervalo) @pag_pago_qtd = pagos.count @pag_pago_valor = pagos.sum(:valor_total) # PENDENTES: consolidações finalizadas cujo PERÍODO cruza o filtro, ainda sem # pagamento (pendente não tem data de pagamento para filtrar). finalizadas_ids = @consolidacoes_mes.where(status: :finalizada).select(:id) pendentes = ConsolidacaoMotorista.where(consolidacao_id: finalizadas_ids).pendentes @pag_pend_qtd = pendentes.count @pag_pend_valor = pendentes.sum(:valor_total) @pagamentos_feitos = pagos.includes(:consolidacao, :pagador).order(pago_em: :desc) @pagamentos_pendentes = pendentes.includes(:consolidacao).order(consolidacao_id: :desc) end def build_grafico_diario(entregas, preco_entrega) dias = (@periodo_inicio..@periodo_fim).to_a if dias.size <= MAX_DIAS_GRAFICO # Granularidade diária (períodos curtos) contagem = entregas.pagas.group("DATE(planned_date)").count.transform_keys(&:to_s) labels = dias.map { |d| d.strftime('%d/%m') } valores = dias.map { |d| ((contagem[d.to_s] || 0) * preco_entrega).round(2) } qtds = dias.map { |d| contagem[d.to_s] || 0 } else # Período longo: agrega por mês para o gráfico não ficar vazio nem poluído. contagem = entregas.pagas.group("TO_CHAR(planned_date, 'YYYY-MM')").count meses = meses_no_periodo labels = meses.map { |m| Date.strptime(m, '%Y-%m').strftime('%m/%Y') } valores = meses.map { |m| ((contagem[m] || 0) * preco_entrega).round(2) } qtds = meses.map { |m| contagem[m] || 0 } end { labels: labels, valores: valores, qtds: qtds } end # Meses ("YYYY-MM") do início ao fim do período selecionado, inclusive. def meses_no_periodo meses = [] cursor = @periodo_inicio.beginning_of_month while cursor <= @periodo_fim meses << cursor.strftime('%Y-%m') cursor = cursor.next_month end meses end end