# app/controllers/consolidacao_entregas_controller.rb class ConsolidacaoEntregasController < ApplicationController include Auditavel before_action :set_consolidacao before_action :bloquear_se_finalizada, except: %i[validar revisar] # GET /consolidacoes/:consolidacao_id/consolidacao_entregas/validar?motorista=X # WIZARD PASSO 2 — tela de validação com toggles def validar authorize @consolidacao, :show? @motorista = params[:motorista] # Entregas pagas do período no banco existente (read-only) @entregas = Entrega.pagas .da_conta_gade .no_periodo(@consolidacao.data_inicio, @consolidacao.data_fim) .do_motorista(@motorista) @entregas = @entregas.da_veiculo(@consolidacao.vehicle_ids) if @consolidacao.vehicle_ids.present? @entregas = @entregas.order(:planned_date) # Classificações já feitas — agrupadas por tracking_id (uma entrega pode ter # vários pilares). { tracking_id => [ConsolidacaoEntrega, ...] } @classificacoes = @consolidacao.consolidacao_entregas .where(motorista_nome: @motorista) .group_by(&:tracking_id) @total_entregas = @consolidacao.entregas_elegiveis_count(@motorista) @classificadas = @consolidacao.classificadas_count(@motorista) # Filtro "apenas não classificadas" if params[:apenas_pendentes] == '1' @entregas = @entregas.reject { |e| @classificacoes.key?(e.tracking_id) } end @precos = { entrega_normal: Configuracao.preco_entrega, retirada: Configuracao.preco_retirada, bonus: Configuracao.preco_bonus, desconto: Configuracao.preco_desconto } end # POST /consolidacoes/:consolidacao_id/consolidacao_entregas/classificar # Classifica UMA entrega (chamado pelo toggle via Turbo/fetch) def classificar authorize @consolidacao, :update? return render_nao_elegivel unless entrega_elegivel?(params[:motorista], params[:tracking_id]) # Toggle do pilar: se já existe, remove; senão, adiciona (permite combinar # vários pilares na mesma entrega). ce = @consolidacao.consolidacao_entregas .find_by(tracking_id: params[:tracking_id], tipo: params[:tipo]) if ce ce.destroy ativo = false else @consolidacao.consolidacao_entregas.create!( tracking_id: params[:tracking_id], motorista_nome: params[:motorista], tipo: params[:tipo], valor_aplicado: valor_para_tipo(params[:tipo]), created_by: current_user.id ) ativo = true end recalcular_motorista(params[:motorista]) respond_to do |format| format.json do render json: resumo_json(params[:motorista]).merge( ativo: ativo, tracking_id: params[:tracking_id], tipo: params[:tipo] ) end format.html { redirect_back fallback_location: wizard_consolidacao_path(@consolidacao) } end end # POST /consolidacoes/:consolidacao_id/consolidacao_entregas/classificar_em_massa # Ações em massa: marcar todos / marcar selecionados def classificar_em_massa authorize @consolidacao, :update? tipo = params[:tipo] motorista = params[:motorista] tracking_ids = Array(params[:tracking_ids]).reject(&:blank?) return render_nao_elegivel unless motorista_na_consolidacao?(motorista) elegiveis = tracking_ids_elegiveis(motorista) # "Marcar todos como Normal" → sem tracking_ids = todas as elegíveis do período. # Com tracking_ids → mantém só as que de fato são elegíveis para o motorista. tracking_ids = tracking_ids.empty? ? elegiveis : (tracking_ids & elegiveis) valor = valor_para_tipo(tipo) # Em massa, ADICIONA o pilar às entregas (não remove os demais já marcados). ActiveRecord::Base.transaction do tracking_ids.each do |tid| ce = @consolidacao.consolidacao_entregas.find_or_initialize_by(tracking_id: tid, tipo: tipo) ce.assign_attributes(motorista_nome: motorista, valor_aplicado: valor, created_by: current_user.id) ce.save! end end recalcular_motorista(motorista) auditar!(:editar, @consolidacao, dados_novos: { acao_massa: tipo, qtd: tracking_ids.size, motorista: motorista }) respond_to do |format| format.json { render json: resumo_json(motorista) } format.html do redirect_to validar_consolidacao_consolidacao_entregas_path(@consolidacao, motorista: motorista), notice: "#{tracking_ids.size} entregas marcadas como #{tipo.humanize}." end end end # GET /consolidacoes/:consolidacao_id/consolidacao_entregas/revisar?motorista=X # WIZARD PASSO 3 — revisão do motorista def revisar authorize @consolidacao, :show? @motorista = params[:motorista] @classificacoes = @consolidacao.consolidacao_entregas .where(motorista_nome: @motorista) .order(:created_at) @resumo = @classificacoes.group(:tipo).count @total = @classificacoes.sum do |c| c.tipo == 'desconto' ? -c.valor_aplicado : c.valor_aplicado end # Próximo motorista pendente (para botão "Próximo Motorista") nomes_completos = @consolidacao.consolidacao_motoristas.map(&:motorista_nome) @proximo = nomes_completos.find do |nome| next false if nome == @motorista total = @consolidacao.entregas_elegiveis_count(nome) @consolidacao.classificadas_count(nome) < total end end private def set_consolidacao @consolidacao = Consolidacao.find(params[:consolidacao_id]) end def bloquear_se_finalizada return unless @consolidacao.finalizada? && current_user.operador? redirect_to @consolidacao, alert: 'Consolidação finalizada — apenas admin/gerente podem editar.' end # Motorista realmente faz parte desta consolidação? def motorista_na_consolidacao?(nome) nome.present? && @consolidacao.consolidacao_motoristas.exists?(motorista_nome: nome) end # 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_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/veículo da # consolidação — impede classificar tracking_ids arbitrários via requisição. def entrega_elegivel?(motorista, tracking_id) motorista_na_consolidacao?(motorista) && tracking_id.present? && tracking_ids_elegiveis(motorista).include?(tracking_id) end def render_nao_elegivel respond_to do |format| format.json { render json: { ok: false, erro: 'Entrega não elegível para este motorista.' }, status: :unprocessable_entity } format.html do redirect_to validar_consolidacao_consolidacao_entregas_path(@consolidacao, motorista: params[:motorista]), alert: 'Entrega não elegível para este motorista.' end end end def valor_para_tipo(tipo) case tipo.to_s when 'entrega_normal' then Configuracao.preco_entrega when 'retirada' then Configuracao.preco_retirada when 'bonus' then Configuracao.preco_bonus when 'desconto' then Configuracao.preco_desconto else 0 end end def recalcular_motorista(nome) cm = @consolidacao.consolidacao_motoristas.find_by(motorista_nome: nome) return unless cm total = @consolidacao.consolidacao_entregas.where(motorista_nome: nome).sum do |e| e.tipo == 'desconto' ? -e.valor_aplicado : e.valor_aplicado end cm.update_column(:valor_total, total) @consolidacao.update_column(:valor_total, @consolidacao.consolidacao_motoristas.sum(:valor_total)) end def resumo_json(motorista) classificacoes = @consolidacao.consolidacao_entregas.where(motorista_nome: motorista) { ok: true, classificadas: classificacoes.distinct.count(:tracking_id), por_tipo: classificacoes.group(:tipo).count, valor_total: classificacoes.sum { |e| e.tipo == 'desconto' ? -e.valor_aplicado : e.valor_aplicado }.to_f } end end