Alteração Para uso dos Usuarios
This commit is contained in:
184
app/services/pdf/extrato_pdf.rb
Normal file
184
app/services/pdf/extrato_pdf.rb
Normal file
@@ -0,0 +1,184 @@
|
||||
# app/services/pdf/extrato_pdf.rb
|
||||
#
|
||||
# Extrato: resumo do valor a pagar ao motorista, estilo contracheque,
|
||||
# com QR Code para login rápido no painel do motorista.
|
||||
#
|
||||
require 'rqrcode'
|
||||
|
||||
module Pdf
|
||||
class ExtratoPdf < BasePdf
|
||||
VERDE = '22C55E'
|
||||
CINZA_BG = 'E5E7EB'
|
||||
|
||||
def initialize(consolidacao:, motorista:, user_motorista: nil, app_host: nil,
|
||||
pago_em: nil, forma_pagamento: nil, nota_fiscal: nil)
|
||||
super()
|
||||
@consolidacao = consolidacao
|
||||
@motorista = motorista
|
||||
@user_motorista = user_motorista
|
||||
@app_host = app_host || ENV.fetch('APP_HOST', 'localhost:3000')
|
||||
@pago_em = pago_em
|
||||
@forma_pagamento = forma_pagamento
|
||||
@nota_fiscal = nota_fiscal
|
||||
@entregas = consolidacao.consolidacao_entregas.where(motorista_nome: motorista)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def corpo
|
||||
secao "EXTRATO DE PAGAMENTO — ENTREGAS"
|
||||
|
||||
# Bloco de identificação (estilo contracheque)
|
||||
info = [
|
||||
['Motorista', @motorista],
|
||||
['Consolidação', @consolidacao.nome],
|
||||
['Período', "#{@consolidacao.data_inicio.strftime('%d/%m/%Y')} a #{@consolidacao.data_fim.strftime('%d/%m/%Y')}"],
|
||||
['Emissão', Time.current.strftime('%d/%m/%Y')]
|
||||
]
|
||||
info << ['Nota Fiscal', @nota_fiscal] if @nota_fiscal.present?
|
||||
@pdf.table(info, width: @pdf.bounds.width, cell_style: { size: 10, padding: [4, 8] }) do |t|
|
||||
t.columns(0).font_style = :bold
|
||||
t.columns(0).background_color = 'F3F4F6'
|
||||
t.columns(0).width = 120
|
||||
end
|
||||
|
||||
desenhar_selo_pagamento
|
||||
|
||||
secao "PROVENTOS E DESCONTOS"
|
||||
|
||||
resumo = @entregas.group(:tipo).count
|
||||
# Lista os quatro pilares sempre (Normal, Retirada, Bônus, Desconto),
|
||||
# mesmo zerados, para o motorista ver o descritivo completo.
|
||||
por_tipo = ConsolidacaoEntrega::TIPO_CORES.map do |tipo, cfg|
|
||||
qtd = resumo[tipo] || 0
|
||||
valor = qtd.zero? ? 0 : @entregas.where(tipo: tipo).sum(:valor_aplicado)
|
||||
unit = qtd.zero? ? 0 : (valor / qtd)
|
||||
sinal = tipo == 'desconto' ? '-' : '+'
|
||||
[cfg[:label], qtd.to_s, moeda(unit), "#{sinal} #{moeda(valor)}"]
|
||||
end
|
||||
|
||||
linhas = [['Descrição', 'Qtd', 'Unit.', 'Valor']] + por_tipo
|
||||
@pdf.table(linhas, header: true, width: @pdf.bounds.width,
|
||||
cell_style: { size: 10, padding: [4, 8] }) do |t|
|
||||
t.row(0).background_color = PRETO
|
||||
t.row(0).text_color = 'FFFFFF'
|
||||
t.row(0).font_style = :bold
|
||||
t.columns(1..3).align = :right
|
||||
end
|
||||
|
||||
total = @entregas.sum { |e| e.desconto? ? -e.valor_aplicado : e.valor_aplicado }
|
||||
|
||||
# Caixa do total — destaque laranja
|
||||
@pdf.move_down 12
|
||||
@pdf.fill_color LARANJA
|
||||
@pdf.fill_rounded_rectangle [0, @pdf.cursor], @pdf.bounds.width, 50, 8
|
||||
@pdf.fill_color '000000'
|
||||
@pdf.text_box 'VALOR LÍQUIDO A RECEBER',
|
||||
at: [16, @pdf.cursor - 10], size: 10, style: :bold
|
||||
@pdf.text_box moeda(total),
|
||||
at: [16, @pdf.cursor - 24], size: 22, style: :bold
|
||||
# >= altura da caixa (50) para a próxima linha não a sobrepor.
|
||||
@pdf.move_down 56
|
||||
|
||||
# Detalhamento: total de entregas e veículos atendidos no período
|
||||
total_entregas = @entregas.count
|
||||
veiculos = @entregas.map { |e| e.entrega_original&.vehicle }.compact.uniq
|
||||
@pdf.fill_color CINZA
|
||||
@pdf.text "Total de entregas: #{total_entregas}", size: 9
|
||||
@pdf.text "Veículos atendidos: #{veiculos.any? ? veiculos.join(', ') : '—'}", size: 9
|
||||
@pdf.fill_color '000000'
|
||||
@pdf.move_down 4
|
||||
|
||||
# QR Code de login rápido. O Prawn não pagina imagem sozinho: se faltar
|
||||
# espaço, garantimos uma nova página para o QR não sumir no rodapé.
|
||||
# Motorista sem conta (sem login_token) não tem painel para acessar —
|
||||
# mostramos um aviso no lugar do QR, em vez de omitir a seção.
|
||||
if @user_motorista&.login_token.present?
|
||||
@pdf.start_new_page if @pdf.cursor < 180
|
||||
desenhar_qrcode
|
||||
else
|
||||
@pdf.start_new_page if @pdf.cursor < 120
|
||||
desenhar_aviso_sem_acesso
|
||||
end
|
||||
|
||||
assinaturas(motorista: @motorista)
|
||||
end
|
||||
|
||||
# Selo de pagamento: faixa verde "PAGO" (com data/forma) ou cinza "PENDENTE".
|
||||
def desenhar_selo_pagamento
|
||||
@pdf.move_down 10
|
||||
pago = @pago_em.present?
|
||||
cor = pago ? VERDE : CINZA_BG
|
||||
texto = if pago
|
||||
via = @forma_pagamento.present? ? " · #{@forma_pagamento.to_s.capitalize}" : ''
|
||||
"✓ PAGO em #{@pago_em.strftime('%d/%m/%Y')}#{via}"
|
||||
else
|
||||
"PAGAMENTO PENDENTE"
|
||||
end
|
||||
|
||||
@pdf.fill_color cor
|
||||
@pdf.fill_rounded_rectangle [0, @pdf.cursor], 250, 26, 5
|
||||
@pdf.fill_color(pago ? 'FFFFFF' : '374151')
|
||||
@pdf.text_box texto, at: [12, @pdf.cursor - 8], width: 238, size: 10, style: :bold
|
||||
@pdf.fill_color '000000'
|
||||
# >= altura da caixa (26) para o conteúdo seguinte não a sobrepor.
|
||||
@pdf.move_down 32
|
||||
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 = url_acesso
|
||||
qr = RQRCode::QRCode.new(url)
|
||||
png = qr.as_png(size: 220, border_modules: 2)
|
||||
|
||||
arquivo = Tempfile.new(['qr', '.png'])
|
||||
arquivo.binmode
|
||||
arquivo.write(png.to_s)
|
||||
arquivo.rewind
|
||||
|
||||
secao "ACESSO RÁPIDO AO SEU PAINEL"
|
||||
|
||||
qr_lado = 95
|
||||
topo = @pdf.cursor
|
||||
# Posição absoluta (at:) NÃO move o cursor — evita o cálculo frágil de
|
||||
# move_up/move_down que sobrepunha o QR ao texto/assinaturas. Depois
|
||||
# avançamos o cursor manualmente pela altura da imagem (quadrada).
|
||||
@pdf.image arquivo.path, at: [0, topo], width: qr_lado
|
||||
@pdf.text_box "Escaneie este QR Code com a câmera do celular para entrar " \
|
||||
"no seu painel e acompanhar seus pagamentos.\n\n" \
|
||||
"Ou acesse: #{@app_host.to_s.sub(%r{\Ahttps?://}, '').chomp('/')}/motorista\ne use seu PIN de 4 dígitos.",
|
||||
at: [qr_lado + 20, topo], width: @pdf.bounds.width - qr_lado - 20, size: 9
|
||||
@pdf.move_down qr_lado + 8
|
||||
ensure
|
||||
arquivo&.close
|
||||
arquivo&.unlink
|
||||
end
|
||||
|
||||
# Exibido quando o motorista não tem conta de acesso (sem login_token).
|
||||
# Sem conta não há painel para o QR apontar, então avisamos no lugar.
|
||||
def desenhar_aviso_sem_acesso
|
||||
secao "ACESSO AO SEU PAINEL"
|
||||
|
||||
altura = 56
|
||||
topo = @pdf.cursor
|
||||
@pdf.fill_color 'FEF3C7' # amarelo claro (aviso)
|
||||
@pdf.fill_rounded_rectangle [0, topo], @pdf.bounds.width, altura, 6
|
||||
@pdf.fill_color '92400E' # âmbar escuro (texto)
|
||||
@pdf.text_box "Cadastro de acesso ainda não disponível para este motorista.\n" \
|
||||
"Solicite ao administrador para receber seu PIN de 4 dígitos e o " \
|
||||
"QR Code de acesso ao painel do motorista.",
|
||||
at: [12, topo - 12], width: @pdf.bounds.width - 24, size: 10
|
||||
@pdf.fill_color '000000'
|
||||
@pdf.move_down altura + 8
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user