Automação de impressoras térmicas diretamente pelo SQL Server
Autor: Fabiano Cores | Publicado: 2011-06-26T22:10:00-03:00

*Este artigo se aplica a versões 2005 ou superior do SQL Server. Na versão 2000, haverá restrições de 8000 caracteres na geração de arquivos, devido a limitação do varchar(max) do SQL 2000. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; O título pode soar um tanto estranho. Mas o fato é que esta se revelou uma grande solução “surpresa” durante o desenvolvimento de um projeto de automação através de coletores de dados. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Para explicar melhor o contexto onde isto foi adotado, tudo começou durante a implantação de um projeto de automação na conferência de mercadorias, confecção de embalagens e expedição de volumes. Adotamos 3 coletores ARGOX PT-60 (fig.1) com Windows CE 5.0 e conexão wireless e 3 impressoras ZEBRA GK420t (fig.2) instaladas em um único computador dedicado, compartilhando as impressoras pela rede. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;         Fig.1 Coletor Argox PT-60  Fig.2 Impressora Térmica ZEBRA GK420t &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; O sistema, nomeado ARKAB, foi desenvolvido em vb.net utilizando o MS Compact framework. Basicamente, o sistema atuava de forma on-line com o servidor SQL, buscando dados referente os pedidos de venda, e alimentando tabelas durante a conferência, possibilitando que dois operadores pudessem conferir o mesmo pedido.  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Este sistema permite conferir as mercadorias que estão no pedido de venda. O separador pega todos os produtos utilizando o romaneio de separação (fig.3), e entrega em um carrinho para o conferente. Este identifica o pedido no coletor através de código de barras (fig.3), identifica-se através do seu crachá pessoal e também identifica qual impressora irá utilizar (código de barras na impressora, conf. fig.4). &nbsp;        Fig.3 Romaneio de Separação Fig.4 Identificação da impressora &nbsp;&nbsp;&nbsp;&nbsp; Mas aí surgiram problemas, que prejudicaram o funcionamento do projeto. Durante o final do mês, a empresa realiza muito processamento no servidor na geração de lotes contábeis, fechamentos fiscais, emissão de DREs pesadas e abarrotadas de dados. E os coletores começaram a ficar muito lentos, prejudicando significativamente a performance da equipe de separação.      &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Outro problema que ocorria sempre era ao enviar uma impressão pela rede. De alguma forma, o Windows CE exibe uma messagebox com o resultado do comando de impressão ao usuário, o que atrapalha (e muito!) durante a conferência. A mensagem aparece na tela, o usuário não percebe, e ele bipa um produto. Como no final do comando sempre tem um ENTER, a tela de mensagem é fechada, e o usuário nem teria percebido que aquele produto bipado somente fechou a mensagem, e não foi processado pelo software. Isto acontecia por que eu gerava um arquivo texto temporário no coletor, e o copiava pela rede diretamente para a impressora, e o próprio Windows CE gerencia esta mensagem, o que parece não ser possível desativar.   &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Para resolver isto, modifiquei o sistema do coletor, fazendo com que carregasse em DATASET todos os dados necessários para realizar a conferência, de forma que acessasse o banco de dados duas vezes (na abertura e no fechamento da conferência), e não mais em cada operação. O aumento de performance foi absoluto, o coletor não dependia mais da rede para ter um bom desempenho ou não. Mas ainda restava a etiqueta. Como fazer a impressão de forma que não aparecesse esta mensagem de status de impressão ao usuário? Resolvi então fazer a impressão da etiqueta térmica utliizando o próprio SQL Server, de forma que o servidor enviasse o comando para a impressora, e não mais o coletor. A solução aparentemente muito simples e funcional me fez perceber que podemos fazer qualquer sistema que utilize base SQL Server imprimir diretamente em impressoras térmicas de linguagem específica (PPLA, PPLB, ZPL, etc).  Para isso, vamos dividir esta informação em três partes fundamentais: geração dos dados da etiqueta, salvar estes dados em um arquivo texto e finalmente copiar este arquivo diretamente à impressora desejada pela rede. 1. Geração dos dados da etiqueta O primeiro procedimento que montei é o que realiza a busca de todas as informações no banco de dados para a impressão da etiqueta, e monta dinamicamente todos os comandos ZPL necessários para a impressão da etiqueta:   Code Snippet   -- set nocount on -- exec spArkabEtiqueta 9 create proc  [dbo].[spArkabEtiqueta]   @id_volume int , @cd_impressora int as &nbsp;   /*   Autor: Fabiano Cores   Data : 06/2011   Objetivo: Gera a etiqueta através de comandos ZPL, salva em arquivo   através de automação OLE e lança o arquivo diretamente para a impressora   térmica na rede através de simples compartilhamento, somente copiando   o arquivo para o dispositivo (como &#92;&#92;pc-na-rede&#92;nome_imp_zebra)   */ &nbsp;   declare @t table (texto varchar(8000) null, ordem int not null identity)      declare @cd_empresa     int   declare @nr_pedido      int   declare @nr_volume      int   declare @qt_volume      int   declare @cd_usuario     int   declare @qt_pecas       int   declare @cliente        varchar(60)   declare @transportadora varchar(60)   declare @endereco       varchar(200)   declare @complemento    varchar(200)   declare @pedido         varchar(20)   declare @volume         varchar(20)   declare @responsavel    varchar(50)   declare @data           varchar(20)   declare @quantidade     varchar(20)   declare @cdb            varchar(50)   declare @vtexto         varchar(8000)   declare @texto          varchar(max)   declare @diretorio      varchar(255)   declare @arquivo        varchar(100)   declare @origem         varchar(500)   declare @destino        varchar(500)      select @cd_empresa = cd_empresa        , @nr_pedido  = nr_pedido        , @nr_volume  = nr_volume        , @cd_usuario = cd_usuario        , @cdb        = cdb     from ArkabVolume    where id_volume   = @id_volume       select @qt_pecas = sum(qt_item)     from arkabvolumeitem    where id_Volume = @id_volume      select @qt_volume = count(*)     from ArkabVolume    where cd_empresa = @cd_empresa      and nr_pedido  = @nr_pedido       select @cliente = rtrim(cadastro.razao)        , @transportadora = isnull(rtrim(transp.razao),&#39;&#39;)        , @endereco = rtrim(cadastro.endereco)        , @complemento = rtrim(ltrim( rtrim(isnull(cadastro.complemento, &#39;&#39;))             + &#39; &#39; + rtrim(cidade.nome) + &#39;/&#39; + rtrim(cidade.cd_estado)             + &#39; - &#39; + left(rtrim(cadastro.cep),5) + &#39;-&#39; + right(rtrim(cadastro.cep),3)))        , @pedido = right(&#39;000000&#39; + rtrim(@nr_pedido), 6)        , @volume = right(&#39;000&#39; + rtrim(@nr_volume),3)--  + &#39;/&#39; + right(&#39;00&#39; + rtrim(@qt_volume),2)        , @responsavel = rtrim(usuario.login)        , @data = convert(char(10), getdate(), 103) + &#39; &#39;           + right(&#39;00&#39; + rtrim(datepart(hour, getdate())),2) + &#39;h&#39;        , @quantidade = rtrim(@qt_pecas) + &#39; PC&#39;             from cadastro, cidade, usuario, pedidovenda, cadastro transp    where pedidovenda.cd_cadastro = cadastro.cd_cadastro      and cidade.cd_Cidade = cadastro.cd_cidade      and pedidovenda.cd_empresa = @cd_empresa      and pedidovenda.nr_pedido  = @nr_pedido      and usuario.cd_usuario = @cd_usuario      and pedidovenda.cd_transportador *= transp.cd_cadastro         select @texto = &#39; ^XA ^MD10 ^LT0 ^JMA^FS ^MUd,200,200 ^LH6,0 ^FO20,7 ^GB780,625,5,,0 ^FS ^FO20,70 ^GB645,120,5,,0 ^FS ^FO20,7 ^GB780,70,70,,0 ^FS ^FO660,70 ^GB140,562,5,,0 ^FS ^FO20,275 ^GB235,160,5,,0 ^FS ^FO30,305 ^GB215,120,120,,0 ^FS ^FO250,275 ^GB180,160,5,,0 ^FS ^FO260,305 ^GB160,120,120,,0 ^FS ^FO425,275 ^GB240,85,5,,0 ^FS ^FO425,355 ^GB240,80,5,,0 ^FS ^FO425,430 ^GB240,75,5,,0 ^FS ^FO425,500 ^GB240,132,5,,0 ^FS ^FO90,20,0 ^ATN,30,20 ^FR ^FD DECORLUX MATERIAL ELETRICO LTDA ^FS ^FO30,80,0 ^AQN,20,10 ^FD CLIENTE ^FS ^FO30,103,0 ^ATN,30,20 ^FD &#39; + left(rtrim(@cliente), 29) + &#39; ^FS ^FO30,138,0 ^ATN,30,20 ^FD &#39; + substring(@cliente, 30, 29) + &#39; ^FS ^FO30,138,0 ^ASN,30,20 ^FD ^FS ^FO30,200,0 ^AQN,20,10 ^FD TRANSPORTADORA ^FS ^FO30,223,0 ^ASN,30,20 ^FD &#39; + rtrim(@transportadora) + &#39; ^FS ^FO30,280,0 ^AQN,20,10 ^FD PEDIDO DE VENDA ^FS ^FO40,290,0 ^AVN,120,100 ^FR ^FD &#39; + rtrim(@pedido) + &#39; ^FS ^FO265,280,0 ^AQN,20,10 ^FD VOLUME ^FS ^FO290,290,0 ^AVN,120,100 ^FR ^FD &#39; + rtrim(@volume) + &#39; ^FS ^FO435,280,0 ^AQN,20,10 ^FD RESPONSAVEL ^FS ^FO435,310,0 ^ASN,20,10 ^FD &#39; + rtrim(@responsavel) + &#39; ^FS &nbsp; ^FO435,360,0 ^AQN,20,10 ^FD DATA ^FS &nbsp; ^FO435,385,0 ^ASN,30,20 ^FD &#39; + rtrim(@data) + &#39; ^FS &nbsp; ^FO435,435,0 ^AQN,20,10 ^FD QUANTIDADE ^FS &nbsp; ^FO435,460,0 ^ASN,30,20 ^FD &#39; + rtrim(@quantidade) + &#39; ^FS &nbsp; ^FO30,435,0 ^AQN,20,10 ^FD NOTA FISCAL E VISTO ^FS &nbsp; ^FO690,97 ^BY2 ^BCR,100,Y,N,N ^FD &#39; + rtrim(@cdb) + &#39; ^FS ^XZ&#39; &nbsp;   -- Diretorio para gravar o arquivo temporário   set @diretorio = &#39;D:&#92;Arkab&#92;impressoes&#92;&#39;      -- Definição do nome do arquivo, coloca dia, mes, ano,   -- hora, minuto, segundo e ID do volume.   select @arquivo = &#39;tmp&#39;                    + right(&#39;00&#39; + rtrim(datepart(day, getdate())),2)                   + right(&#39;00&#39; + rtrim(datepart(month, getdate())),2)                   + rtrim(datepart(year, getdate()))                   + right(&#39;00&#39; + rtrim(datepart(hour, getdate())),2)                   + right(&#39;00&#39; + rtrim(datepart(minute, getdate())),2)                   + right(&#39;00&#39; + rtrim(datepart(second, getdate())),2)                   + &#39;_vol_&#39; + rtrim(@id_volume) + &#39;.txt&#39;                      -- Gravo o arquivo   exec spArkabGravaArquivo     @texto, @diretorio, @arquivo        -- Definição da origem e destino do arquivo salvo.   select @origem = rtrim(@diretorio) + rtrim(@arquivo) &nbsp;   -- Algo como &#39;&#92;&#92;PC-CONF01&#92;ZEBRA-SEP1&#39;   select @destino = caminho_de_rede     from ArkabImpressora    where cd_impressora = @cd_impressora      -- Envio o arquivo texto pela rede diretamente na impressora térmica.   exec spArkabCopiaArquivo     @origem, @destino     Este procedimento coleta todos os dados, concatena em um varchar(max), e passa esta string para a segunda etapa, que é gravar este texto em um arquivo em uma pasta específica no servidor. Exemplo do texto gerado pelo procedimento:       ^XA^MD10^LT0^JMA^FS^MUd,200,200^LH6,0^FO20,7^GB780,625,5,,0^FS^FO20,70^GB645,120,5,,0^FS^FO20,7^GB780,70,70,,0^FS^FO660,70^GB140,562,5,,0^FS^FO20,275^GB235,160,5,,0^FS^FO30,305^GB215,120,120,,0^FS^FO250,275^GB180,160,5,,0^FS^FO260,305^GB160,120,120,,0^FS^FO425,275^GB240,85,5,,0^FS^FO425,355^GB240,80,5,,0^FS^FO425,430^GB240,75,5,,0^FS^FO425,500^GB240,132,5,,0^FS^FO90,20,0^ATN,30,20^FR^FDDECORLUX MATERIAL ELETRICO LTDA^FS^FO30,80,0^AQN,20,10^FDCLIENTE^FS^FO30,103,0^ATN,30,20^FDTORNADO INDUSTRIA E PECAS LTD^FS^FO30,138,0^ATN,30,20^FDA^FS^FO30,138,0^ASN,30,20^FD^FS^FO30,200,0^AQN,20,10^FDTRANSPORTADORA^FS^FO30,223,0^ASN,30,20^FDNATALIN DA SILVA RAMOS^FS^FO30,280,0^AQN,20,10^FDPEDIDO DE VENDA^FS^FO40,290,0^AVN,120,100^FR^FD056783^FS^FO265,280,0^AQN,20,10^FDVOLUME^FS^FO290,290,0^AVN,120,100^FR^FD009^FS^FO435,280,0^AQN,20,10^FDRESPONSAVEL^FS^FO435,310,0^ASN,20,10^FDAUTOMATICO^FS  ^FO435,360,0^AQN,20,10^FDDATA^FS  ^FO435,385,0^ASN,30,20^FD26/06/2011 21h^FS  ^FO435,435,0^AQN,20,10^FDQUANTIDADE^FS  ^FO435,460,0^ASN,30,20^FD1 PC^FS  ^FO30,435,0^AQN,20,10^FDNOTA FISCAL E VISTO^FS  ^FO690,97^BY2^BCR,100,Y,N,N^FD00020567830099699335^FS^XZ Veja que depois defino qual o destino final do arquivo (no caso, uma impressora zebra na rede). No caso do sistema Arkab, a definição da impressora é dinâmica, no momento da abertura da conferência o usuário seleciona qual irá utilizar. Ao imprimir a etiqueta, o sistema passa para o procedimento como parâmetro (@cd_impressora) qual será a impressora que deverá enviar o comando. Uma simples tabela permite referenciar o código desta impressora ao seu caminho de rede. E logo no final, temos o procedimento spArkabCopiaArquivo, que faz a cópia do arquivo do servidor para a impressora utilizando a rede. Vamos então falar de cada um deles agora. &nbsp; 2. spArkabGravaArquivo - Este procedimento grava o texto desejado em um arquivo em disco:    Code Snippet   create proc spArkabGravaArquivo   @texto varchar(max), --8000 in SQL Server 2000   @diretorio varchar(255),   @arquivo varchar(100) &nbsp; as  &nbsp;   /*   Autor: Fabiano Cores   Data : 06/2011   Objetivo: Permite gravar arquivos de texto em disco diretamente   através do T-SQL. &nbsp;   Observação importante: pode haver a necessidade de ativar a   opção &#39;Ole Automation Procedures&#39; no SQL Server através do comando   sp_reconfigure. Para isto, utilize o script abaixo:      -- Mostrar opções avançadas.   exec sp_configure &#39;show advanced options&#39;, 1   GO   -- Ativar a opção anterior   reconfigure   GO   -- Ativa a automação OLE   exec sp_configure &#39;Ole Automation Procedures&#39;, 1   GO   -- Reconfigura para aceitar a modificação   reconfigure   GO   */ &nbsp;   declare @objFileSystem   int   declare @objTextStream   int   declare @hr              int   declare @fileAndPath     varchar(80) &nbsp;   exec @hr = sp_OACreate &#39;Scripting.FileSystemObject&#39;                        , @objFileSystem output &nbsp;   select @FileAndPath = @diretorio + &#39;&#92;&#39; + @arquivo      if @hr = 0      exec @hr = sp_OAMethod @objFileSystem                          , &#39;CreateTextFile&#39;                          , @objTextStream output                          , @FileAndPath                          , 2                          , True &nbsp;   if @hr=0     exec @hr = sp_OAMethod @objTextStream                          , &#39;Write&#39;                          , Null                          , @texto &nbsp;   if @hr = 0     exec @hr = sp_OAMethod @objTextStream, &#39;Close&#39; &nbsp;   exec sp_OADestroy @objTextStream   exec sp_OADestroy @objTextStream      &nbsp; 3. spArkabCopiaArquivo – Realiza a cópia do arquivo da etiqueta diretamente para a impressora:       /* -- Para testar, copia o arquivo direto para impressora SEP1. exec spArkabCopiaArquivo      &#39;D:&#92;Arkab&#92;Impressoes&#92;tmp26062011030828_vol_4.txt&#39;   , &#39;&#92;&#92;PC-CONF01&#92;ZEBRA-SEP1&#39; */ create proc spArkabCopiaArquivo   @origem varchar(200) , @destino varchar(200) &nbsp; as &nbsp;   /*   Autor: Fabiano Cores   Data : 06/2011   Objetivo: Permite copiar arquivos, informando origem e destino. &nbsp;   Observação importante: pode haver a necessidade de ativar a   opção &#39;xp_cmdshell&#39; no SQL Server através do comando   sp_reconfigure. Para isto, utilize o script abaixo:      -- Mostrar opções avançadas.   exec sp_configure &#39;show advanced options&#39;, 1   GO   -- Ativar a opção anterior   reconfigure   GO   -- Ativa o comando   exec sp_configure &#39;xp_cmdshell&#39;, 1   GO   -- Reconfigura para aceitar a modificação   reconfigure   GO   */ &nbsp;   declare @string varchar(400)      select @string = &#39;copy &#39; + rtrim(@origem) + &#39; &#39; + rtrim(@destino) &nbsp;   exec master.dbo.xp_cmdshell @string, no_output      &nbsp; O resultado, é uma etiqueta saindo pela impressora, como esta daqui de exemplo (exatamente a mesma do procedimento acima): &nbsp;      &nbsp; Uma solução interessante…