Correção do PDF QRCODE holerite
This commit is contained in:
25
README.md
25
README.md
@@ -591,3 +591,28 @@ Conjunto de melhorias para deixar o sistema pronto para entrega final.
|
|||||||
20260617000002_add_operacoes_to_consolidacoes.rb
|
20260617000002_add_operacoes_to_consolidacoes.rb
|
||||||
```
|
```
|
||||||
Aplicar com: `docker-compose exec app bundle exec rails db:migrate`
|
Aplicar com: `docker-compose exec app bundle exec rails db:migrate`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 Correções — Holerite PDF (18/06/2026)
|
||||||
|
|
||||||
|
### 🔴 Bugs corrigidos
|
||||||
|
- **500 ao gerar holerite** (`gerar_pdf_holerite`): `Prawn::Errors::IncompatibleStringEncoding`
|
||||||
|
(`Encoding::UndefinedConversionError`). Os PDFs usavam a fonte **embutida** do Prawn
|
||||||
|
(Helvetica/AFM), que só aceita o charset **Windows-1252** — o caractere **`✓`** do selo
|
||||||
|
"✓ PAGO" não existe nesse charset e derrubava a geração.
|
||||||
|
✅ **Corrigido:** `BasePdf` agora registra a família **DejaVu Sans (TTF)** e a define como
|
||||||
|
fonte padrão (`registrar_fonte_utf8`), com suporte total a **UTF-8**. Vale para todos os PDFs
|
||||||
|
(holerite e relatório) — qualquer caractere Unicode passa a funcionar.
|
||||||
|
- Fontes versionadas em `app/assets/fonts/DejaVuSans.ttf` e `DejaVuSans-Bold.ttf`
|
||||||
|
(a imagem Docker `ruby:3.2.2-slim` não traz fontes do sistema; o `COPY . .` do Dockerfile e
|
||||||
|
o volume `.:/app` garantem que estejam disponíveis em runtime).
|
||||||
|
- **Assinaturas do holerite caíam na 2ª página:** após o QR Code o cursor ficava abaixo do
|
||||||
|
limite de quebra (`start_new_page if cursor < 120`), empurrando o bloco de assinaturas para
|
||||||
|
uma página nova. ✅ **Corrigido:** espaçamentos do holerite compactados (selo, tabelas, caixa
|
||||||
|
do total, bloco do QR menor) + folga antes das assinaturas reduzida (`BasePdf#assinaturas`,
|
||||||
|
60 → 36) — **o holerite agora cabe em uma única página**.
|
||||||
|
|
||||||
|
### 📌 Padrão (novo)
|
||||||
|
- **PDFs com Prawn**: usar **sempre** a fonte TTF registrada no `BasePdf` (DejaVu, UTF-8). Nunca
|
||||||
|
contar com a fonte embutida — ela quebra em qualquer caractere fora do Windows-1252.
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ module Pdf
|
|||||||
# (direita). Garante espaço criando nova página se o rodapé estiver perto.
|
# (direita). Garante espaço criando nova página se o rodapé estiver perto.
|
||||||
def assinaturas(motorista: nil)
|
def assinaturas(motorista: nil)
|
||||||
@pdf.start_new_page if @pdf.cursor < 120
|
@pdf.start_new_page if @pdf.cursor < 120
|
||||||
@pdf.move_down 60
|
@pdf.move_down 36
|
||||||
|
|
||||||
metade = (@pdf.bounds.width - 40) / 2.0
|
metade = (@pdf.bounds.width - 40) / 2.0
|
||||||
topo = @pdf.cursor
|
topo = @pdf.cursor
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ module Pdf
|
|||||||
['Período', "#{@consolidacao.data_inicio.strftime('%d/%m/%Y')} a #{@consolidacao.data_fim.strftime('%d/%m/%Y')}"],
|
['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')]
|
['Emissão', Time.current.strftime('%d/%m/%Y')]
|
||||||
]
|
]
|
||||||
@pdf.table(info, width: @pdf.bounds.width, cell_style: { size: 10, padding: [5, 8] }) do |t|
|
@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).font_style = :bold
|
||||||
t.columns(0).background_color = 'F3F4F6'
|
t.columns(0).background_color = 'F3F4F6'
|
||||||
t.columns(0).width = 120
|
t.columns(0).width = 120
|
||||||
@@ -57,7 +57,7 @@ module Pdf
|
|||||||
|
|
||||||
linhas = [['Descrição', 'Qtd', 'Unit.', 'Valor']] + por_tipo
|
linhas = [['Descrição', 'Qtd', 'Unit.', 'Valor']] + por_tipo
|
||||||
@pdf.table(linhas, header: true, width: @pdf.bounds.width,
|
@pdf.table(linhas, header: true, width: @pdf.bounds.width,
|
||||||
cell_style: { size: 10, padding: [6, 8] }) do |t|
|
cell_style: { size: 10, padding: [4, 8] }) do |t|
|
||||||
t.row(0).background_color = PRETO
|
t.row(0).background_color = PRETO
|
||||||
t.row(0).text_color = 'FFFFFF'
|
t.row(0).text_color = 'FFFFFF'
|
||||||
t.row(0).font_style = :bold
|
t.row(0).font_style = :bold
|
||||||
@@ -67,7 +67,7 @@ module Pdf
|
|||||||
total = @entregas.sum { |e| e.desconto? ? -e.valor_aplicado : e.valor_aplicado }
|
total = @entregas.sum { |e| e.desconto? ? -e.valor_aplicado : e.valor_aplicado }
|
||||||
|
|
||||||
# Caixa do total — destaque laranja
|
# Caixa do total — destaque laranja
|
||||||
@pdf.move_down 16
|
@pdf.move_down 12
|
||||||
@pdf.fill_color LARANJA
|
@pdf.fill_color LARANJA
|
||||||
@pdf.fill_rounded_rectangle [0, @pdf.cursor], @pdf.bounds.width, 50, 8
|
@pdf.fill_rounded_rectangle [0, @pdf.cursor], @pdf.bounds.width, 50, 8
|
||||||
@pdf.fill_color '000000'
|
@pdf.fill_color '000000'
|
||||||
@@ -75,7 +75,7 @@ module Pdf
|
|||||||
at: [16, @pdf.cursor - 10], size: 10, style: :bold
|
at: [16, @pdf.cursor - 10], size: 10, style: :bold
|
||||||
@pdf.text_box moeda(total),
|
@pdf.text_box moeda(total),
|
||||||
at: [16, @pdf.cursor - 24], size: 22, style: :bold
|
at: [16, @pdf.cursor - 24], size: 22, style: :bold
|
||||||
@pdf.move_down 58
|
@pdf.move_down 48
|
||||||
|
|
||||||
# Detalhamento: total de entregas e veículos atendidos no período
|
# Detalhamento: total de entregas e veículos atendidos no período
|
||||||
total_entregas = @entregas.count
|
total_entregas = @entregas.count
|
||||||
@@ -84,7 +84,7 @@ module Pdf
|
|||||||
@pdf.text "Total de entregas: #{total_entregas}", size: 9
|
@pdf.text "Total de entregas: #{total_entregas}", size: 9
|
||||||
@pdf.text "Veículos atendidos: #{veiculos.any? ? veiculos.join(', ') : '—'}", size: 9
|
@pdf.text "Veículos atendidos: #{veiculos.any? ? veiculos.join(', ') : '—'}", size: 9
|
||||||
@pdf.fill_color '000000'
|
@pdf.fill_color '000000'
|
||||||
@pdf.move_down 6
|
@pdf.move_down 4
|
||||||
|
|
||||||
# QR Code de login rápido. O Prawn não pagina imagem sozinho: se faltar
|
# 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é.
|
# espaço, garantimos uma nova página para o QR não sumir no rodapé.
|
||||||
@@ -113,7 +113,7 @@ module Pdf
|
|||||||
@pdf.fill_color(pago ? 'FFFFFF' : '374151')
|
@pdf.fill_color(pago ? 'FFFFFF' : '374151')
|
||||||
@pdf.text_box texto, at: [12, @pdf.cursor - 8], width: 238, size: 10, style: :bold
|
@pdf.text_box texto, at: [12, @pdf.cursor - 8], width: 238, size: 10, style: :bold
|
||||||
@pdf.fill_color '000000'
|
@pdf.fill_color '000000'
|
||||||
@pdf.move_down 34
|
@pdf.move_down 20
|
||||||
end
|
end
|
||||||
|
|
||||||
# Monta a URL respeitando o esquema do APP_HOST. Se vier sem http(s)://
|
# Monta a URL respeitando o esquema do APP_HOST. Se vier sem http(s)://
|
||||||
@@ -137,13 +137,13 @@ module Pdf
|
|||||||
arquivo.rewind
|
arquivo.rewind
|
||||||
|
|
||||||
secao "ACESSO RÁPIDO AO SEU PAINEL"
|
secao "ACESSO RÁPIDO AO SEU PAINEL"
|
||||||
@pdf.image arquivo.path, width: 110, position: :left
|
@pdf.image arquivo.path, width: 92, position: :left
|
||||||
@pdf.move_up 95
|
@pdf.move_up 80
|
||||||
@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.to_s.sub(%r{\Ahttps?://}, '').chomp('/')}/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: [120, @pdf.cursor], width: @pdf.bounds.width - 120, size: 9
|
||||||
@pdf.move_down 100
|
@pdf.move_down 85
|
||||||
ensure
|
ensure
|
||||||
arquivo&.close
|
arquivo&.close
|
||||||
arquivo&.unlink
|
arquivo&.unlink
|
||||||
|
|||||||
Reference in New Issue
Block a user