Correção do QR do holerite entre outras coisas
This commit is contained in:
@@ -90,14 +90,21 @@ class ConsolidacaoEntregasController < ApplicationController
|
|||||||
return render_nao_elegivel unless motorista_na_consolidacao?(motorista)
|
return render_nao_elegivel unless motorista_na_consolidacao?(motorista)
|
||||||
|
|
||||||
elegiveis = tracking_ids_elegiveis(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.
|
# 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)
|
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
|
ActiveRecord::Base.transaction do
|
||||||
|
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|
|
tracking_ids.each do |tid|
|
||||||
ce = @consolidacao.consolidacao_entregas.find_or_initialize_by(tracking_id: tid, tipo: tipo)
|
ce = @consolidacao.consolidacao_entregas.find_or_initialize_by(tracking_id: tid, tipo: tipo)
|
||||||
ce.assign_attributes(motorista_nome: motorista,
|
ce.assign_attributes(motorista_nome: motorista,
|
||||||
@@ -105,16 +112,19 @@ class ConsolidacaoEntregasController < ApplicationController
|
|||||||
ce.save!
|
ce.save!
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
recalcular_motorista(motorista)
|
recalcular_motorista(motorista)
|
||||||
auditar!(:editar, @consolidacao,
|
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|
|
respond_to do |format|
|
||||||
format.json { render json: resumo_json(motorista) }
|
format.json { render json: resumo_json(motorista) }
|
||||||
format.html do
|
format.html do
|
||||||
|
verbo = remover ? 'desmarcadas de' : 'marcadas como'
|
||||||
redirect_to validar_consolidacao_consolidacao_entregas_path(@consolidacao, motorista: motorista),
|
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
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ import { Controller } from "@hotwired/stimulus"
|
|||||||
|
|
||||||
export default class extends Controller {
|
export default class extends Controller {
|
||||||
static targets = [
|
static targets = [
|
||||||
"linha", "checkbox", "selecionarTodos", "toggles", "barra", "percentual",
|
"linha", "checkbox", "selecionarTodos", "modoRemover", "rotuloAcao",
|
||||||
|
"toggles", "barra", "percentual",
|
||||||
"contadorClassificadas", "contadorTotal",
|
"contadorClassificadas", "contadorTotal",
|
||||||
"qtdNormal", "qtdRetirada", "qtdBonus", "qtdDesconto", "valorTotal"
|
"qtdNormal", "qtdRetirada", "qtdBonus", "qtdDesconto", "valorTotal"
|
||||||
]
|
]
|
||||||
@@ -53,14 +54,17 @@ export default class extends Controller {
|
|||||||
// ── Marcar todos como Normal ───────────────────────────────
|
// ── Marcar todos como Normal ───────────────────────────────
|
||||||
async marcarTodos(event) {
|
async marcarTodos(event) {
|
||||||
const tipo = event.currentTarget.dataset.tipo
|
const tipo = event.currentTarget.dataset.tipo
|
||||||
if (!confirm(`Marcar TODAS as entregas como ${this.labelTipo(tipo)}?`)) return
|
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, {
|
const resp = await this.post(this.massaUrlValue, {
|
||||||
tipo: tipo,
|
tipo: tipo,
|
||||||
motorista: this.motoristaValue,
|
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)
|
this.atualizarResumo(resp)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,6 +74,18 @@ export default class extends Controller {
|
|||||||
this.checkboxTargets.forEach(c => { c.checked = marcar })
|
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 ─────────────────────────────
|
// ── Marcar selecionados como X ─────────────────────────────
|
||||||
async marcarSelecionados(event) {
|
async marcarSelecionados(event) {
|
||||||
const tipo = event.currentTarget.dataset.tipo
|
const tipo = event.currentTarget.dataset.tipo
|
||||||
@@ -80,12 +96,14 @@ export default class extends Controller {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const remover = this.modoRemover
|
||||||
const resp = await this.post(this.massaUrlValue, {
|
const resp = await this.post(this.massaUrlValue, {
|
||||||
tipo: tipo,
|
tipo: tipo,
|
||||||
motorista: this.motoristaValue,
|
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)
|
this.checkboxTargets.forEach(c => c.checked = false)
|
||||||
if (this.hasSelecionarTodosTarget) this.selecionarTodosTarget.checked = false
|
if (this.hasSelecionarTodosTarget) this.selecionarTodosTarget.checked = false
|
||||||
this.atualizarResumo(resp)
|
this.atualizarResumo(resp)
|
||||||
|
|||||||
@@ -55,6 +55,40 @@ module Pdf
|
|||||||
"R$ #{format('%.2f', v.to_f)}".gsub('.', ',')
|
"R$ #{format('%.2f', v.to_f)}".gsub('.', ',')
|
||||||
end
|
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)
|
def secao(titulo)
|
||||||
@pdf.move_down 14
|
@pdf.move_down 14
|
||||||
@pdf.fill_color LARANJA
|
@pdf.fill_color LARANJA
|
||||||
|
|||||||
@@ -69,10 +69,22 @@ module Pdf
|
|||||||
|
|
||||||
# QR Code de login rápido
|
# QR Code de login rápido
|
||||||
desenhar_qrcode if @user_motorista&.login_token.present?
|
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
|
end
|
||||||
|
|
||||||
def desenhar_qrcode
|
def desenhar_qrcode
|
||||||
url = "https://#{@app_host}/motorista/acesso/#{@user_motorista.login_token}"
|
url = url_acesso
|
||||||
qr = RQRCode::QRCode.new(url)
|
qr = RQRCode::QRCode.new(url)
|
||||||
png = qr.as_png(size: 220, border_modules: 2)
|
png = qr.as_png(size: 220, border_modules: 2)
|
||||||
|
|
||||||
@@ -86,7 +98,7 @@ module Pdf
|
|||||||
@pdf.move_up 95
|
@pdf.move_up 95
|
||||||
@pdf.text_box "Escaneie este QR Code com a câmera do celular para entrar\n" \
|
@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" \
|
"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
|
at: [130, @pdf.cursor], width: @pdf.bounds.width - 130, size: 9
|
||||||
@pdf.move_down 100
|
@pdf.move_down 100
|
||||||
ensure
|
ensure
|
||||||
|
|||||||
@@ -29,12 +29,12 @@ module Pdf
|
|||||||
secao "ENTREGAS (#{@entregas.size})"
|
secao "ENTREGAS (#{@entregas.size})"
|
||||||
|
|
||||||
if @entregas.any?
|
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|
|
@entregas.each_with_index do |e, i|
|
||||||
original = e.entrega_original
|
original = e.entrega_original
|
||||||
linhas << [
|
linhas << [
|
||||||
(i + 1).to_s,
|
(i + 1).to_s,
|
||||||
e.tracking_id.to_s.first(14),
|
original&.vehicle.presence || '—',
|
||||||
original ? "NF #{original.numero_nf}" : '—',
|
original ? "NF #{original.numero_nf}" : '—',
|
||||||
original ? original.address.to_s.first(45) : '—',
|
original ? original.address.to_s.first(45) : '—',
|
||||||
e.tipo_cor[:label],
|
e.tipo_cor[:label],
|
||||||
@@ -79,6 +79,8 @@ module Pdf
|
|||||||
@pdf.fill_color LARANJA
|
@pdf.fill_color LARANJA
|
||||||
@pdf.text "TOTAL A PAGAR: #{moeda(total)}", size: 16, style: :bold
|
@pdf.text "TOTAL A PAGAR: #{moeda(total)}", size: 16, style: :bold
|
||||||
@pdf.fill_color '000000'
|
@pdf.fill_color '000000'
|
||||||
|
|
||||||
|
assinaturas(motorista: @motorista)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
<table class="w-full text-sm">
|
<table class="w-full text-sm">
|
||||||
<thead class="bg-[#0a0a0a] text-gray-400">
|
<thead class="bg-[#0a0a0a] text-gray-400">
|
||||||
<tr>
|
<tr>
|
||||||
<th class="text-left px-4 py-3">Tracking / NF</th>
|
<th class="text-left px-4 py-3">Veículo</th>
|
||||||
<th class="text-left px-4 py-3">Endereço</th>
|
<th class="text-left px-4 py-3">Endereço</th>
|
||||||
<th class="text-left px-4 py-3">Tipo</th>
|
<th class="text-left px-4 py-3">Tipo</th>
|
||||||
<th class="text-right px-4 py-3">Valor</th>
|
<th class="text-right px-4 py-3">Valor</th>
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
<tbody class="divide-y divide-[#2a2a2a]">
|
<tbody class="divide-y divide-[#2a2a2a]">
|
||||||
<% @classificacoes.each do |c| %>
|
<% @classificacoes.each do |c| %>
|
||||||
<tr>
|
<tr>
|
||||||
<td class="px-4 py-3 text-white font-mono text-xs"><%= c.tracking_id %></td>
|
<td class="px-4 py-3 text-white text-xs"><%= c.entrega_original&.vehicle.presence || '—' %></td>
|
||||||
<td class="px-4 py-3 text-gray-300 text-xs"><%= c.entrega_original&.address.presence || '—' %></td>
|
<td class="px-4 py-3 text-gray-300 text-xs"><%= c.entrega_original&.address.presence || '—' %></td>
|
||||||
<td class="px-4 py-3">
|
<td class="px-4 py-3">
|
||||||
<span class="<%= c.tipo_cor[:bg] %> <%= c.tipo_cor[:text] %> px-2 py-1 rounded text-xs font-bold">
|
<span class="<%= c.tipo_cor[:bg] %> <%= c.tipo_cor[:text] %> px-2 py-1 rounded text-xs font-bold">
|
||||||
|
|||||||
@@ -52,8 +52,14 @@
|
|||||||
Selecionar todos
|
Selecionar todos
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
|
<label class="flex items-center gap-2 text-sm cursor-pointer select-none border-l border-[#2a2a2a] pl-3">
|
||||||
|
<input type="checkbox" data-validacao-target="modoRemover" data-action="change->validacao#alternarModo"
|
||||||
|
class="rounded text-red-500 bg-[#0a0a0a] border-[#2a2a2a] w-5 h-5">
|
||||||
|
<span class="text-gray-300">Modo remover (desmarcar)</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<span class="text-gray-500 text-sm">Selecionados como:</span>
|
<span class="text-gray-500 text-sm" data-validacao-target="rotuloAcao">Selecionados como:</span>
|
||||||
<button data-action="click->validacao#marcarSelecionados" data-tipo="entrega_normal"
|
<button data-action="click->validacao#marcarSelecionados" data-tipo="entrega_normal"
|
||||||
class="bg-orange-500 text-black font-bold px-3 py-2 rounded-lg text-xs min-h-[44px]">Normal</button>
|
class="bg-orange-500 text-black font-bold px-3 py-2 rounded-lg text-xs min-h-[44px]">Normal</button>
|
||||||
<button data-action="click->validacao#marcarSelecionados" data-tipo="retirada"
|
<button data-action="click->validacao#marcarSelecionados" data-tipo="retirada"
|
||||||
|
|||||||
Reference in New Issue
Block a user