Print Service Unificado — Documento de Contexto
Consolidado da conversa sobre automação de impressão no server1 (Windows Server). Cobre arquitetura, decisões, regras de domínio, código e passos de implementação.
Este service é a implementação técnica do que acontece na transição Pago → Ag. Impressão descrita em geracao-req-producao e conferencia-automatica-ag-impressao. Quando o webhook dispara, o print service coordena a impressão de fita, fichas e rótulos — os documentos que a produção consome.
1. Problema original
Na Neofarma, a impressão de rótulos na Epson ColorWorks C6000AU é manual: o operador abre o PDF no Adobe Reader, seleciona um preset de tamanho (85x25, 85x30, 85x40, 85x45mm), escolhe “rolo contínuo” como media source, confere no preview, e imprime. A largura é sempre 85mm; a altura varia conforme o produto. O rolo não tem marcas de detecção — o corte depende exclusivamente da altura informada ao driver.
O objetivo é eliminar essa intervenção manual, integrando a impressão de rótulos (e de fichas e fitas) num único fluxo automático disparado por evento do ERP.
2. Decisões de arquitetura
2.1 Evento único como gatilho
Quando o pedido muda de “pago” para “ag.impressão” na VPS, um webhook dispara POST /api/print-order no server1 via Cloudflare Tunnel. Nesse momento, todos os PDFs de rótulo já estão salvos na pasta do Google Drive. O service não precisa de polling, retry ou espera — os arquivos estarão lá.
2.2 Três impressões em sequência
| Job | Impressora | Tecnologia | Quem gera o conteúdo |
|---|---|---|---|
| Fita | Bematech MP4200 | ESC/POS direto (socket/serial) | Dados do pedido via ERP |
| Fichas | Brother A4 | PAD (UI automation) → PDF → SumatraPDF | PAD abre ERP Delphi, gera PDF |
| Rótulos | Epson C6000AU | Ghostscript → driver Windows (mswinpr2) | VPS gera PDFs, salva no Drive |
A sequência é: fita → fichas → rótulos. Sequencial para evitar conflito no spooler.
2.3 Pasta unificada no Google Drive
G:\REQ\{numReq}\
├── ficha-{numReq}.pdf ← gerado pelo PAD (server1)
├── {numReq}-a.pdf ← rótulo produto A (gerado pela VPS)
├── {numReq}-b.pdf ← rótulo produto B (gerado pela VPS)
└── {numReq}-c.pdf ← rótulo produto C (gerado pela VPS)
Convenção de nomes: fichas começam com ficha-, rótulos não. O service filtra por esse prefixo.
2.4 Ghostscript como renderizador (não .NET, não SumatraPDF para rótulos)
Ghostscript foi escolhido porque:
- Controla
DEVICEWIDTHPOINTSeDEVICEHEIGHTPOINTSpor job via CLI - Usa o device
mswinpr2que imprime via driver Windows padrão - Não precisa de código .NET, PowerShell complexo, ou registrar Windows Forms dinâmicos
- É CLI puro, chamável via
child_processdo Node
Limitação aceita: Ghostscript não controla media source (Roll Paper vs. Cut Sheet). A solução é configurar “Roll Paper” como default nas preferências do driver da Epson, uma vez só, no Painel de Controle do Windows.
2.5 PAD para fichas (não API, não GS direto)
O ERP Delphi não tem API de exportação de PDF. A única forma de extrair a ficha da requisição é via UI automation — o PAD simula: abrir ERP → navegar à REQ → clicar imprimir → selecionar “Microsoft Print to PDF” → salvar em G:\REQ\{numReq}\. Depois, o próprio PAD imprime na Brother via SumatraPDF CLI.
O Node chama o PAD via PAD.Console.Host.exe run --flow "Imprimir REQ" --input NumeroReq={numReq} e espera o processo terminar (timeout de 2 min).
Ver automacao-req-pad-erp para detalhamento completo do fluxo PAD.
2.6 SQLite para logs (não Firebird, não Postgres)
O SQLite local (C:\PrintService\print-log.db) registra cada job de impressão. Não usa o Firebird do ERP porque o log de impressão é operacional do server1, não dado de negócio. Consultável via API do próprio service.
2.7 Resposta assíncrona (202 Accepted)
O endpoint retorna 202 imediatamente para a VPS não ficar bloqueada. O processamento roda em background. A VPS pode consultar o status depois via GET /api/print-log/:orderId.
3. Regras de domínio — Rótulos e Epson C6000AU
3.1 Como o driver da Epson decide onde cortar
O C6000AU em rolo contínuo sem marcas depende do DEVMODE do print job para saber a altura do papel. Quando o Adobe Reader usa presets, ele está populando dmPaperLength no DEVMODE. O Ghostscript faz o equivalente via -dDEVICEHEIGHTPOINTS.
3.2 Dimensões e conversões
- Largura fixa: 85mm
- Altura: variável, extraída do MediaBox do PDF
- Presets disponíveis: 25, 30, 40, 45mm (ordenados)
- Conversão:
1pt = 1/72 polegada,1 polegada = 25.4mm ptToMm(pt) = (pt / 72) * 25.4mmToPt(mm) = (mm / 25.4) * 72
3.3 Lógica de seleção de altura
// Snap para o menor preset que cabe o conteúdo
const preset = [25, 30, 40, 45].find(h => h >= contentHeightMm);
// Se conteúdo maior que todos os presets → usa altura exata arredondada
if (!preset) return Math.ceil(contentHeightMm);Existe flag useExactHeight que, se true, ignora presets e usa a altura exata do PDF arredondada para cima ao próximo 0.5mm.
3.4 Extração da altura do PDF
const { PDFDocument } = require('pdf-lib');
const bytes = await readFile(pdfPath);
const doc = await PDFDocument.load(bytes);
const page = doc.getPage(0);
const { height } = page.getMediaBox(); // em pontos
const heightMm = ptToMm(height);3.5 Flags do Ghostscript para rótulos
gswin64c.exe
-dBATCH -dNOPAUSE -dNOSAFER
-sDEVICE=mswinpr2
-sOutputFile=%printer%Epson ColorWorks C6000AU
-dDEVICEWIDTHPOINTS={largura em pt}
-dDEVICEHEIGHTPOINTS={altura em pt}
-dFitPage ← escala conteúdo ao tamanho do papel
-dNoCancel ← sem diálogo de impressão
arquivo.pdf
-dFitPage pode ser removido se os PDFs já são gerados com exatamente 85mm de largura.
3.6 Flags do Ghostscript para fichas A4
gswin64c.exe
-dBATCH -dNOPAUSE -dNOSAFER
-sDEVICE=mswinpr2
-sOutputFile=%printer%Brother HL-XXXX series
-sPAPERSIZE=a4
-dFitPage -dNoCancel
arquivo.pdf
(Na prática, a Brother é chamada pelo PAD via SumatraPDF, não pelo service diretamente.)
4. Regras de domínio — Fichas e PAD
4.1 Detecção de REQs novas
O PAD consulta o Firebird do ERP via ODBC:
SELECT NUMERO_REQ, DATA_INCLUSAO
FROM REQUISICAO
WHERE DATA_INCLUSAO > @ultima_verificacao
ORDER BY DATA_INCLUSAOCompara contra a tabela SQLite processamentos para filtrar já processadas.
4.2 Controle SQLite do PAD
CREATE TABLE processamentos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
numero_req TEXT NOT NULL,
data_inclusao TEXT,
status TEXT DEFAULT 'pendente', -- pendente | ok | erro
erro TEXT,
processado_em TEXT DEFAULT (datetime('now','localtime'))
);4.3 UI Automation — pontos de atenção
- Waits de 2-3s entre ações (ERP Delphi tem latência variável)
- Diálogo “Salvar como” do Print to PDF é janela do sistema — path preenchido dinamicamente
- Verificação pós-salvamento:
If file existsno PDF gerado - Timeout de erro: se o ERP travar, o PAD precisa abortar e registrar erro no SQLite
4.4 Impressão na Brother via SumatraPDF
& "C:\Tools\SumatraPDF\SumatraPDF.exe" -print-to "Brother HL-XXXX series" -silent "G:\REQ\{numReq}\ficha-{numReq}.pdf"SumatraPDF é preferido sobre Start-Process -Verb PrintTo porque é silencioso e fecha sozinho.
5. Regras de domínio — Fita e MP4200
A MP4200 é controlada via ESC/POS direto (raw socket ou serial), sem passar pelo driver Windows. O código ESC/POS já existe e funciona — hoje é disparado por botão, passa a ser disparado pela função printFita(orderId) no service.
A fita não envolve PDF nem Ghostscript — é construção de buffer de bytes ESC/POS puro.
6. API do Print Service
6.1 Endpoints
POST /api/print-order
Body: { "orderId": "PV-12345", "reqNumber": "123456" }
Response: 202 { "message": "Pedido PV-12345 (REQ 123456) aceito para impressão" }
Comportamento: processa em background, não bloqueia
GET /api/print-log/:orderId
Response: { "orderId": "...", "jobs": [...] }
GET /api/print-log?limit=50
Response: { "jobs": [...] } (últimos N jobs, default 50, max 500)
6.2 Schema do SQLite de log
CREATE TABLE print_jobs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
order_id TEXT NOT NULL,
job_type TEXT NOT NULL CHECK(job_type IN ('fita', 'fichas', 'rotulos')),
file_name TEXT,
status TEXT NOT NULL CHECK(status IN ('ok', 'error', 'skipped')),
detail TEXT,
paper_height_mm REAL,
created_at TEXT DEFAULT (datetime('now', 'localtime'))
);6.3 Exemplo de log
order_id | job_type | file_name | status | detail | paper_height_mm
PV-123 | fita | NULL | ok | NULL | NULL
PV-123 | fichas | ficha-456789.pdf | ok | NULL | NULL
PV-123 | rotulos | 456789-a.pdf | ok | conteúdo: 22.3mm → papel: 25mm | 25
PV-123 | rotulos | 456789-b.pdf | ok | conteúdo: 38.1mm → papel: 40mm | 40
7. Configuração do service
const CONFIG = {
port: 3100,
gsPath: 'gswin64c.exe',
padExePath: 'C:\\Program Files\\Power Automate Desktop\\PAD.Console.Host.exe',
padFlowName: 'Imprimir REQ',
printers: {
fita: 'Bematech MP-4200 TH',
fichas: 'Brother HL-XXXX series',
rotulos: 'Epson ColorWorks C6000AU',
},
rotulo: {
widthMm: 85,
presetHeightsMm: [25, 30, 40, 45],
},
driveBasePath: 'G:\\REQ',
dbPath: 'C:\\PrintService\\print-log.db',
};Todos os nomes de impressoras devem ser exatos conforme Get-Printer | Select-Object Name no PowerShell.
8. Pré-requisitos no Server1
| Software | Versão | Propósito |
|---|---|---|
| Node.js | 18+ | Runtime do service |
| Ghostscript | 10.x (64-bit) | Renderizar PDFs e enviar ao driver Windows |
| SumatraPDF | Portable | Impressão silenciosa de fichas na Brother (via PAD) |
| Power Automate Desktop | — | UI automation no ERP Delphi |
| Firebird ODBC | 2.5 | PAD consulta banco do ERP |
| Google Drive for Desktop | — | Monta G:\ com pastas de REQ |
| Cloudflare Tunnel | — | Expõe porta 3100 para a VPS |
Dependências npm: express, pdf-lib, better-sqlite3
Configurações one-time no Windows
- Driver Epson C6000AU → Preferências → Paper Source: Roll Paper
- Ghostscript no PATH do sistema (
C:\Program Files\gs\gs10.x\bin) - DSN Firebird ODBC configurado para o banco do ERP
- CF Tunnel roteando para
localhost:3100
9. Diagrama da arquitetura
VPS (nuvem)
│
evento "pago → ag.impressão"
+ rótulos já salvos em G:\REQ\{req}\ via Drive
│
CF Tunnel (HTTPS)
│
▼
┌─────────────────────┐
│ Print Service │
│ (Node + Express) │
│ porta 3100 │
│ SQLite log local │
└────────┬────────────┘
│
┌─────────────┼─────────────┐
▼ ▼ ▼
1. FITA 2. FICHAS 3. RÓTULOS
MP4200 Brother A4 Epson C6000AU
ESC/POS PAD→PDF→ GS → mswinpr2
direto SumatraPDF (altura variável)
│
PAD abre ERP Delphi
via UI automation
salva PDF em G:\REQ\{req}\
imprime na Brother
Todas as impressoras na rede local do server1
10. Fluxo causal detalhado
1. Atendente finaliza pedido na VPS
2. VPS muda status: "pago" → "ag.impressão"
3. VPS já gerou PDFs de rótulo e salvou em Google Drive (G:\REQ\{numReq}\)
4. VPS envia POST /api/print-order { orderId, reqNumber } via CF Tunnel
5. Server1 recebe, responde 202, inicia processamento background
6. [FITA] Service monta buffer ESC/POS com dados do pedido → envia à MP4200 → fita impressa
7. [FICHAS] Service chama PAD via CLI com reqNumber
7a. PAD abre ERP Delphi
7b. PAD navega à REQ, clica imprimir, salva PDF em G:\REQ\{numReq}\ficha-{numReq}.pdf
7c. PAD chama SumatraPDF → imprime na Brother A4
7d. Service verifica que o PDF existe no disco → log ok
8. [RÓTULOS] Service lista G:\REQ\{numReq}\*.pdf excluindo ficha-*
8a. Para cada PDF de rótulo:
- pdf-lib extrai MediaBox → altura em mm
- Seleciona menor preset ≥ altura (25/30/40/45)
- Ghostscript imprime via mswinpr2 na Epson com dimensão customizada
- Driver corta o rolo na altura informada
- Log com altura do conteúdo e altura do papel
9. Todos os jobs logados no SQLite
10. VPS pode consultar GET /api/print-log/{orderId} para verificar status
11. Decisões ainda abertas
| Tema | Status | Notas |
|---|---|---|
| Nome exato do fluxo PAD | A definir | Depende de como for gravado no recorder |
| Caminho exato do PAD.Console.Host.exe | A verificar | Varia conforme versão instalada |
-dFitPage nos rótulos | A testar | Remover se PDFs já são 85mm de largura |
useExactHeight vs presets | Começar com presets | Testar se preset produz margem indesejada; se sim, mudar para exato |
| Dados necessários para a fita ESC/POS | Integrar código existente | A função printFita está como placeholder |
| Tratamento de concorrência | Sequencial por enquanto | Se volume crescer, avaliar fila (bull/bee-queue) |
12. Referências cruzadas
- automacao-req-pad-erp — detalhamento do fluxo PAD + Firebird ODBC + SQLite
- guia-print-service-implementacao — guia prático passo a passo de implementação
- geracao-req-producao — processo de negócio: geração de REQ (Fases 1-3)
- conferencia-automatica-ag-impressao — processo de negócio: conferência automática e JSON consolidado
- conferencia-producao — processo de negócio: conferências 1 e 2 que consomem os rótulos impressos
- triagem-tecnica — triagem técnica que recebe o pacote impresso
- Conversa “Automação de requisições do ERP com impressão”
- Conversa “Mapeamento de fluxo de pedidos”
- Agente de reconciliação V5
13. Código-fonte
O arquivo print-service.js entregue junto com este documento contém o service completo com:
- Express server na porta 3100
- Endpoint
POST /api/print-order(async, 202) - Endpoints de consulta
GET /api/print-log - Job de fita (placeholder para ESC/POS existente)
- Job de fichas (dispara PAD via CLI)
- Job de rótulos (pdf-lib + Ghostscript com altura variável)
- SQLite de log automático
- CONFIG centralizado para ajustar nomes de impressoras, caminhos e presets