diff --git a/app/controllers/consolidacao_entregas_controller.rb b/app/controllers/consolidacao_entregas_controller.rb index 4f6cc1a..4cce105 100644 --- a/app/controllers/consolidacao_entregas_controller.rb +++ b/app/controllers/consolidacao_entregas_controller.rb @@ -90,31 +90,41 @@ class ConsolidacaoEntregasController < ApplicationController 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. + # Sem tracking_ids = todas as elegíveis do período (botão "todos"). # 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) + remover = params[:acao].to_s == 'remover' - # 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! + if remover + # Desmarca o pilar das entregas selecionadas (mantém os demais pilares). + @consolidacao.consolidacao_entregas + .where(motorista_nome: motorista, tipo: tipo, tracking_id: tracking_ids) + .destroy_all + else + # ADICIONA o pilar às entregas (não remove os demais já marcados). + valor = valor_para_tipo(tipo) + 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 end recalcular_motorista(motorista) auditar!(:editar, @consolidacao, - dados_novos: { acao_massa: tipo, qtd: tracking_ids.size, motorista: motorista }) + dados_novos: { acao_massa: tipo, modo: (remover ? 'remover' : 'adicionar'), + qtd: tracking_ids.size, motorista: motorista }) respond_to do |format| format.json { render json: resumo_json(motorista) } format.html do + verbo = remover ? 'desmarcadas de' : 'marcadas como' redirect_to validar_consolidacao_consolidacao_entregas_path(@consolidacao, motorista: motorista), - notice: "#{tracking_ids.size} entregas marcadas como #{tipo.humanize}." + notice: "#{tracking_ids.size} entregas #{verbo} #{tipo.humanize}." end end end diff --git a/app/javascript/controllers/validacao_controller.js b/app/javascript/controllers/validacao_controller.js index a0c06c5..d5ca85e 100644 --- a/app/javascript/controllers/validacao_controller.js +++ b/app/javascript/controllers/validacao_controller.js @@ -6,7 +6,8 @@ import { Controller } from "@hotwired/stimulus" export default class extends Controller { static targets = [ - "linha", "checkbox", "selecionarTodos", "toggles", "barra", "percentual", + "linha", "checkbox", "selecionarTodos", "modoRemover", "rotuloAcao", + "toggles", "barra", "percentual", "contadorClassificadas", "contadorTotal", "qtdNormal", "qtdRetirada", "qtdBonus", "qtdDesconto", "valorTotal" ] @@ -52,15 +53,18 @@ export default class extends Controller { // ── Marcar todos como Normal ─────────────────────────────── async marcarTodos(event) { - const tipo = event.currentTarget.dataset.tipo - if (!confirm(`Marcar TODAS as entregas como ${this.labelTipo(tipo)}?`)) return + const tipo = event.currentTarget.dataset.tipo + const remover = this.modoRemover + const verbo = remover ? "REMOVER de TODAS as entregas" : "Marcar TODAS as entregas como" + if (!confirm(`${verbo} ${this.labelTipo(tipo)}?`)) return const resp = await this.post(this.massaUrlValue, { tipo: tipo, motorista: this.motoristaValue, - tracking_ids: [] // vazio = todas + tracking_ids: [], // vazio = todas + acao: remover ? "remover" : "adicionar" }) - this.linhaTargets.forEach(l => this.setToggle(l.dataset.tracking, tipo, true)) + this.linhaTargets.forEach(l => this.setToggle(l.dataset.tracking, tipo, !remover)) this.atualizarResumo(resp) } @@ -70,6 +74,18 @@ export default class extends Controller { this.checkboxTargets.forEach(c => { c.checked = marcar }) } + // ── Modo das ações em massa: adicionar (padrão) ou remover ── + get modoRemover() { + return this.hasModoRemoverTarget && this.modoRemoverTarget.checked + } + + alternarModo() { + if (this.hasRotuloAcaoTarget) { + this.rotuloAcaoTarget.textContent = + this.modoRemover ? "Remover dos selecionados:" : "Selecionados como:" + } + } + // ── Marcar selecionados como X ───────────────────────────── async marcarSelecionados(event) { const tipo = event.currentTarget.dataset.tipo @@ -80,12 +96,14 @@ export default class extends Controller { return } + const remover = this.modoRemover const resp = await this.post(this.massaUrlValue, { tipo: tipo, motorista: this.motoristaValue, - tracking_ids: ids + tracking_ids: ids, + acao: remover ? "remover" : "adicionar" }) - ids.forEach(id => this.setToggle(id, tipo, true)) + ids.forEach(id => this.setToggle(id, tipo, !remover)) this.checkboxTargets.forEach(c => c.checked = false) if (this.hasSelecionarTodosTarget) this.selecionarTodosTarget.checked = false this.atualizarResumo(resp) diff --git a/app/services/pdf/base_pdf.rb b/app/services/pdf/base_pdf.rb index e957dc1..83a2953 100644 --- a/app/services/pdf/base_pdf.rb +++ b/app/services/pdf/base_pdf.rb @@ -55,6 +55,40 @@ module Pdf "R$ #{format('%.2f', v.to_f)}".gsub('.', ',') end + # Bloco de assinaturas lado a lado: motorista (esquerda) e administrador + # (direita). Garante espaço criando nova página se o rodapé estiver perto. + def assinaturas(motorista: nil) + @pdf.start_new_page if @pdf.cursor < 120 + @pdf.move_down 60 + + metade = (@pdf.bounds.width - 40) / 2.0 + topo = @pdf.cursor + + @pdf.stroke_color '000000' + @pdf.line_width 0.7 + @pdf.stroke_horizontal_line 0, metade, at: topo + @pdf.stroke_horizontal_line metade + 40, @pdf.bounds.width, at: topo + + @pdf.move_down 8 + linha_nome = @pdf.cursor + @pdf.fill_color '000000' + @pdf.text_box(motorista.presence || 'Motorista', + at: [0, linha_nome], width: metade, size: 9, style: :bold, align: :center) + @pdf.text_box('Reem Transporte', + at: [metade + 40, linha_nome], width: metade, size: 9, style: :bold, align: :center) + + @pdf.move_down 14 + linha_rotulo = @pdf.cursor + @pdf.fill_color CINZA + @pdf.text_box('Assinatura do Motorista', + at: [0, linha_rotulo], width: metade, size: 8, align: :center) + @pdf.text_box('Assinatura do Administrador', + at: [metade + 40, linha_rotulo], width: metade, size: 8, align: :center) + + @pdf.fill_color '000000' + @pdf.move_down 24 + end + def secao(titulo) @pdf.move_down 14 @pdf.fill_color LARANJA diff --git a/app/services/pdf/holerite_pdf.rb b/app/services/pdf/holerite_pdf.rb index 22530fa..2e32557 100644 --- a/app/services/pdf/holerite_pdf.rb +++ b/app/services/pdf/holerite_pdf.rb @@ -69,10 +69,22 @@ module Pdf # QR Code de login rápido desenhar_qrcode if @user_motorista&.login_token.present? + + assinaturas(motorista: @motorista) + end + + # Monta a URL respeitando o esquema do APP_HOST. Se vier sem http(s):// + # (ex.: "100.75.222.23:3000") assume http — produção em IP:porta normalmente + # não tem TLS. Defina APP_HOST com "https://" se o servidor usar HTTPS. + def url_acesso + raw = @app_host.to_s.strip + protocolo = raw.start_with?('https://') ? 'https' : 'http' + host = raw.sub(%r{\Ahttps?://}, '').chomp('/') + "#{protocolo}://#{host}/motorista/acesso/#{@user_motorista.login_token}" end def desenhar_qrcode - url = "https://#{@app_host}/motorista/acesso/#{@user_motorista.login_token}" + url = url_acesso qr = RQRCode::QRCode.new(url) png = qr.as_png(size: 220, border_modules: 2) @@ -86,7 +98,7 @@ module Pdf @pdf.move_up 95 @pdf.text_box "Escaneie este QR Code com a câmera do celular para entrar\n" \ "no seu painel e acompanhar seus pagamentos.\n\n" \ - "Ou acesse: #{@app_host}/motorista\ne use seu PIN de 4 dígitos.", + "Ou acesse: #{@app_host.to_s.sub(%r{\Ahttps?://}, '').chomp('/')}/motorista\ne use seu PIN de 4 dígitos.", at: [130, @pdf.cursor], width: @pdf.bounds.width - 130, size: 9 @pdf.move_down 100 ensure diff --git a/app/services/pdf/relatorio_motorista_pdf.rb b/app/services/pdf/relatorio_motorista_pdf.rb index 17d7868..9ca11f1 100644 --- a/app/services/pdf/relatorio_motorista_pdf.rb +++ b/app/services/pdf/relatorio_motorista_pdf.rb @@ -29,12 +29,12 @@ module Pdf secao "ENTREGAS (#{@entregas.size})" if @entregas.any? - linhas = [['#', 'Tracking', 'NF', 'Endereço', 'Tipo', 'Valor']] + linhas = [['#', 'Veículo', 'NF', 'Endereço', 'Tipo', 'Valor']] @entregas.each_with_index do |e, i| original = e.entrega_original linhas << [ (i + 1).to_s, - e.tracking_id.to_s.first(14), + original&.vehicle.presence || '—', original ? "NF #{original.numero_nf}" : '—', original ? original.address.to_s.first(45) : '—', e.tipo_cor[:label], @@ -79,6 +79,8 @@ module Pdf @pdf.fill_color LARANJA @pdf.text "TOTAL A PAGAR: #{moeda(total)}", size: 16, style: :bold @pdf.fill_color '000000' + + assinaturas(motorista: @motorista) end end end diff --git a/app/views/consolidacao_entregas/revisar.html.erb b/app/views/consolidacao_entregas/revisar.html.erb index eaefbdf..3fdf28c 100644 --- a/app/views/consolidacao_entregas/revisar.html.erb +++ b/app/views/consolidacao_entregas/revisar.html.erb @@ -51,7 +51,7 @@ - + @@ -60,7 +60,7 @@ <% @classificacoes.each do |c| %> - +
Tracking / NFVeículo Endereço Tipo Valor
<%= c.tracking_id %><%= c.entrega_original&.vehicle.presence || '—' %> <%= c.entrega_original&.address.presence || '—' %> diff --git a/app/views/consolidacao_entregas/validar.html.erb b/app/views/consolidacao_entregas/validar.html.erb index 258ab93..34ae985 100644 --- a/app/views/consolidacao_entregas/validar.html.erb +++ b/app/views/consolidacao_entregas/validar.html.erb @@ -52,8 +52,14 @@ Selecionar todos + +
- Selecionados como: + Selecionados como: