Pesquisar este blog

domingo, 26 de junho de 2011

Automação de impressoras térmicas diretamente pelo SQL Server

*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.

       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.

       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.                      

Fig.1 Coletor Argox PT-60 Fig.2 Impressora Térmica ZEBRA GK420t

       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.

        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).

 

image image
Fig.3 Romaneio de Separação Fig.4 Identificação da impressora

     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.

         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.

image

          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
  1. -- set nocount on
  2. -- exec spArkabEtiqueta 9
  3. create proc  [dbo].[spArkabEtiqueta]
  4.   @id_volume int
  5. , @cd_impressora int
  6. as
  7.  
  8.   /*
  9.   Autor: Fabiano Cores
  10.   Data : 06/2011
  11.   Objetivo: Gera a etiqueta através de comandos ZPL, salva em arquivo
  12.   através de automação OLE e lança o arquivo diretamente para a impressora
  13.   térmica na rede através de simples compartilhamento, somente copiando
  14.   o arquivo para o dispositivo (como \\pc-na-rede\nome_imp_zebra)
  15.   */
  16.  
  17.   declare @t table (texto varchar(8000) null, ordem int not null identity)
  18.   
  19.   declare @cd_empresa     int
  20.   declare @nr_pedido      int
  21.   declare @nr_volume      int
  22.   declare @qt_volume      int
  23.   declare @cd_usuario     int
  24.   declare @qt_pecas       int
  25.   declare @cliente        varchar(60)
  26.   declare @transportadora varchar(60)
  27.   declare @endereco       varchar(200)
  28.   declare @complemento    varchar(200)
  29.   declare @pedido         varchar(20)
  30.   declare @volume         varchar(20)
  31.   declare @responsavel    varchar(50)
  32.   declare @data           varchar(20)
  33.   declare @quantidade     varchar(20)
  34.   declare @cdb            varchar(50)
  35.   declare @vtexto         varchar(8000)
  36.   declare @texto          varchar(max)
  37.   declare @diretorio      varchar(255)
  38.   declare @arquivo        varchar(100)
  39.   declare @origem         varchar(500)
  40.   declare @destino        varchar(500)
  41.   
  42.   select @cd_empresa = cd_empresa
  43.        , @nr_pedido  = nr_pedido
  44.        , @nr_volume  = nr_volume
  45.        , @cd_usuario = cd_usuario
  46.        , @cdb        = cdb
  47.     from ArkabVolume
  48.    where id_volume   = @id_volume
  49.    
  50.   select @qt_pecas = sum(qt_item)
  51.     from arkabvolumeitem
  52.    where id_Volume = @id_volume
  53.   
  54.   select @qt_volume = count(*)
  55.     from ArkabVolume
  56.    where cd_empresa = @cd_empresa
  57.      and nr_pedido  = @nr_pedido
  58.    
  59.   select @cliente = rtrim(cadastro.razao)
  60.        , @transportadora = isnull(rtrim(transp.razao),'')
  61.        , @endereco = rtrim(cadastro.endereco)
  62.        , @complemento = rtrim(ltrim( rtrim(isnull(cadastro.complemento, ''))
  63.            + ' ' + rtrim(cidade.nome) + '/' + rtrim(cidade.cd_estado)
  64.            + ' - ' + left(rtrim(cadastro.cep),5) + '-' + right(rtrim(cadastro.cep),3)))
  65.        , @pedido = right('000000' + rtrim(@nr_pedido), 6)
  66.        , @volume = right('000' + rtrim(@nr_volume),3)--  + '/' + right('00' + rtrim(@qt_volume),2)
  67.        , @responsavel = rtrim(usuario.login)
  68.        , @data = convert(char(10), getdate(), 103) + ' '
  69.          + right('00' + rtrim(datepart(hour, getdate())),2) + 'h'
  70.        , @quantidade = rtrim(@qt_pecas) + ' PC'
  71.        
  72.     from cadastro, cidade, usuario, pedidovenda, cadastro transp
  73.    where pedidovenda.cd_cadastro = cadastro.cd_cadastro
  74.      and cidade.cd_Cidade = cadastro.cd_cidade
  75.      and pedidovenda.cd_empresa = @cd_empresa
  76.      and pedidovenda.nr_pedido  = @nr_pedido
  77.      and usuario.cd_usuario = @cd_usuario
  78.      and pedidovenda.cd_transportador *= transp.cd_cadastro
  79.      
  80.   select @texto = '
  81. ^XA
  82. ^MD10
  83. ^LT0
  84. ^JMA^FS
  85. ^MUd,200,200
  86. ^LH6,0
  87. ^FO20,7
  88. ^GB780,625,5,,0
  89. ^FS
  90. ^FO20,70
  91. ^GB645,120,5,,0
  92. ^FS
  93. ^FO20,7
  94. ^GB780,70,70,,0
  95. ^FS
  96. ^FO660,70
  97. ^GB140,562,5,,0
  98. ^FS
  99. ^FO20,275
  100. ^GB235,160,5,,0
  101. ^FS
  102. ^FO30,305
  103. ^GB215,120,120,,0
  104. ^FS
  105. ^FO250,275
  106. ^GB180,160,5,,0
  107. ^FS
  108. ^FO260,305
  109. ^GB160,120,120,,0
  110. ^FS
  111. ^FO425,275
  112. ^GB240,85,5,,0
  113. ^FS
  114. ^FO425,355
  115. ^GB240,80,5,,0
  116. ^FS
  117. ^FO425,430
  118. ^GB240,75,5,,0
  119. ^FS
  120. ^FO425,500
  121. ^GB240,132,5,,0
  122. ^FS
  123. ^FO90,20,0
  124. ^ATN,30,20
  125. ^FR
  126. ^FD
  127. DECORLUX MATERIAL ELETRICO LTDA
  128. ^FS
  129. ^FO30,80,0
  130. ^AQN,20,10
  131. ^FD
  132. CLIENTE
  133. ^FS
  134. ^FO30,103,0
  135. ^ATN,30,20
  136. ^FD
  137. ' + left(rtrim(@cliente), 29) + '
  138. ^FS
  139. ^FO30,138,0
  140. ^ATN,30,20
  141. ^FD
  142. ' + substring(@cliente, 30, 29) + '
  143. ^FS
  144. ^FO30,138,0
  145. ^ASN,30,20
  146. ^FD
  147. ^FS
  148. ^FO30,200,0
  149. ^AQN,20,10
  150. ^FD
  151. TRANSPORTADORA
  152. ^FS
  153. ^FO30,223,0
  154. ^ASN,30,20
  155. ^FD
  156. ' + rtrim(@transportadora) + '
  157. ^FS
  158. ^FO30,280,0
  159. ^AQN,20,10
  160. ^FD
  161. PEDIDO DE VENDA
  162. ^FS
  163. ^FO40,290,0
  164. ^AVN,120,100
  165. ^FR
  166. ^FD
  167. ' + rtrim(@pedido) + '
  168. ^FS
  169. ^FO265,280,0
  170. ^AQN,20,10
  171. ^FD
  172. VOLUME
  173. ^FS
  174. ^FO290,290,0
  175. ^AVN,120,100
  176. ^FR
  177. ^FD
  178. ' + rtrim(@volume) + '
  179. ^FS
  180. ^FO435,280,0
  181. ^AQN,20,10
  182. ^FD
  183. RESPONSAVEL
  184. ^FS
  185. ^FO435,310,0
  186. ^ASN,20,10
  187. ^FD
  188. ' + rtrim(@responsavel) + '
  189. ^FS
  190.  
  191. ^FO435,360,0
  192. ^AQN,20,10
  193. ^FD
  194. DATA
  195. ^FS
  196.  
  197. ^FO435,385,0
  198. ^ASN,30,20
  199. ^FD
  200. ' + rtrim(@data) + '
  201. ^FS
  202.  
  203. ^FO435,435,0
  204. ^AQN,20,10
  205. ^FD
  206. QUANTIDADE
  207. ^FS
  208.  
  209. ^FO435,460,0
  210. ^ASN,30,20
  211. ^FD
  212. ' + rtrim(@quantidade) + '
  213. ^FS
  214.  
  215. ^FO30,435,0
  216. ^AQN,20,10
  217. ^FD
  218. NOTA FISCAL E VISTO
  219. ^FS
  220.  
  221. ^FO690,97
  222. ^BY2
  223. ^BCR,100,Y,N,N
  224. ^FD
  225. ' + rtrim(@cdb) + '
  226. ^FS
  227. ^XZ'
  228.  
  229.   -- Diretorio para gravar o arquivo temporário
  230.   set @diretorio = 'D:\Arkab\impressoes\'
  231.   
  232.   -- Definição do nome do arquivo, coloca dia, mes, ano,
  233.   -- hora, minuto, segundo e ID do volume.
  234.   select @arquivo = 'tmp'
  235.                   + right('00' + rtrim(datepart(day, getdate())),2)
  236.                   + right('00' + rtrim(datepart(month, getdate())),2)
  237.                   + rtrim(datepart(year, getdate()))
  238.                   + right('00' + rtrim(datepart(hour, getdate())),2)
  239.                   + right('00' + rtrim(datepart(minute, getdate())),2)
  240.                   + right('00' + rtrim(datepart(second, getdate())),2)
  241.                   + '_vol_' + rtrim(@id_volume) + '.txt'
  242.                   
  243.   -- Gravo o arquivo
  244.   exec spArkabGravaArquivo
  245.     @texto, @diretorio, @arquivo
  246.     
  247.   -- Definição da origem e destino do arquivo salvo.
  248.   select @origem = rtrim(@diretorio) + rtrim(@arquivo)
  249.  
  250.   -- Algo como '\\PC-CONF01\ZEBRA-SEP1'
  251.   select @destino = caminho_de_rede
  252.     from ArkabImpressora
  253.    where cd_impressora = @cd_impressora
  254.   
  255.   -- Envio o arquivo texto pela rede diretamente na impressora térmica.
  256.   exec spArkabCopiaArquivo
  257.     @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
^FD
DECORLUX MATERIAL ELETRICO LTDA
^FS
^FO30,80,0
^AQN,20,10
^FD
CLIENTE
^FS
^FO30,103,0
^ATN,30,20
^FD
TORNADO INDUSTRIA E PECAS LTD
^FS
^FO30,138,0
^ATN,30,20
^FD
A
^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
NATALIN DA SILVA RAMOS
^FS
^FO30,280,0
^AQN,20,10
^FD
PEDIDO DE VENDA
^FS
^FO40,290,0
^AVN,120,100
^FR
^FD
056783
^FS
^FO265,280,0
^AQN,20,10
^FD
VOLUME
^FS
^FO290,290,0
^AVN,120,100
^FR
^FD
009
^FS
^FO435,280,0
^AQN,20,10
^FD
RESPONSAVEL
^FS
^FO435,310,0
^ASN,20,10
^FD
AUTOMATICO
^FS

^FO435,360,0
^AQN,20,10
^FD
DATA
^FS

^FO435,385,0
^ASN,30,20
^FD
26/06/2011 21h
^FS

^FO435,435,0
^AQN,20,10
^FD
QUANTIDADE
^FS

^FO435,460,0
^ASN,30,20
^FD
1 PC
^FS

^FO30,435,0
^AQN,20,10
^FD
NOTA FISCAL E VISTO
^FS

^FO690,97
^BY2
^BCR,100,Y,N,N
^FD
00020567830099699335
^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.

 

2. spArkabGravaArquivo - Este procedimento grava o texto desejado em um arquivo em disco:

Code Snippet
  1. create proc spArkabGravaArquivo
  2.   @texto varchar(max), --8000 in SQL Server 2000
  3.   @diretorio varchar(255),
  4.   @arquivo varchar(100)
  5.  
  6. as
  7.  
  8.   /*
  9.   Autor: Fabiano Cores
  10.   Data : 06/2011
  11.   Objetivo: Permite gravar arquivos de texto em disco diretamente
  12.   através do T-SQL.
  13.  
  14.   Observação importante: pode haver a necessidade de ativar a
  15.   opção 'Ole Automation Procedures' no SQL Server através do comando
  16.   sp_reconfigure. Para isto, utilize o script abaixo:
  17.   
  18.   -- Mostrar opções avançadas.
  19.   exec sp_configure 'show advanced options', 1
  20.   GO
  21.   -- Ativar a opção anterior
  22.   reconfigure
  23.   GO
  24.   -- Ativa a automação OLE
  25.   exec sp_configure 'Ole Automation Procedures', 1
  26.   GO
  27.   -- Reconfigura para aceitar a modificação
  28.   reconfigure
  29.   GO
  30.   */
  31.  
  32.   declare @objFileSystem   int
  33.   declare @objTextStream   int
  34.   declare @hr              int
  35.   declare @fileAndPath     varchar(80)
  36.  
  37.   exec @hr = sp_OACreate 'Scripting.FileSystemObject'
  38.                        , @objFileSystem output
  39.  
  40.   select @FileAndPath = @diretorio + '\' + @arquivo
  41.   
  42.   if @hr = 0
  43.     exec @hr = sp_OAMethod @objFileSystem
  44.                          , 'CreateTextFile'
  45.                          , @objTextStream output
  46.                          , @FileAndPath
  47.                          , 2
  48.                          , True
  49.  
  50.   if @hr=0
  51.     exec @hr = sp_OAMethod @objTextStream
  52.                          , 'Write'
  53.                          , Null
  54.                          , @texto
  55.  
  56.   if @hr = 0
  57.     exec @hr = sp_OAMethod @objTextStream, 'Close'
  58.  
  59.   exec sp_OADestroy @objTextStream
  60.   exec sp_OADestroy @objTextStream

 

3. spArkabCopiaArquivo – Realiza a cópia do arquivo da etiqueta diretamente para a impressora:

  1. /*
  2. -- Para testar, copia o arquivo direto para impressora SEP1.
  3. exec spArkabCopiaArquivo
  4.     'D:\Arkab\Impressoes\tmp26062011030828_vol_4.txt'
  5.   , '\\PC-CONF01\ZEBRA-SEP1'
  6. */
  7. create proc spArkabCopiaArquivo
  8.   @origem varchar(200)
  9. , @destino varchar(200)
  10.  
  11. as
  12.  
  13.   /*
  14.   Autor: Fabiano Cores
  15.   Data : 06/2011
  16.   Objetivo: Permite copiar arquivos, informando origem e destino.
  17.  
  18.   Observação importante: pode haver a necessidade de ativar a
  19.   opção 'xp_cmdshell' no SQL Server através do comando
  20.   sp_reconfigure. Para isto, utilize o script abaixo:
  21.   
  22.   -- Mostrar opções avançadas.
  23.   exec sp_configure 'show advanced options', 1
  24.   GO
  25.   -- Ativar a opção anterior
  26.   reconfigure
  27.   GO
  28.   -- Ativa o comando
  29.   exec sp_configure 'xp_cmdshell', 1
  30.   GO
  31.   -- Reconfigura para aceitar a modificação
  32.   reconfigure
  33.   GO
  34.   */
  35.  
  36.   declare @string varchar(400)
  37.   
  38.   select @string = 'copy ' + rtrim(@origem) + ' ' + rtrim(@destino)
  39.  
  40.   exec master.dbo.xp_cmdshell @string, no_output

 

O resultado, é uma etiqueta saindo pela impressora, como esta daqui de exemplo (exatamente a mesma do procedimento acima):

2406201168624062011683 

24062011685 24062011684

 

Uma solução interessante…

2 comentários:

  1. Cara, ficou muito bom!
    Aqui na empresa, acabamos de desenvolver exatamente isto: sistema com coletores e distribuição de mercadorias.
    Pegamos um pedido de venda, como trata-se de empresa do ramo de varejo, temos uma grade para os produtos.
    Este problema de impressão de etiquetas, resolvemos colocando um serviço em C# com o Quartz que monitora uma tabela pegando o que ainda está pendente de impressão.
    Temos impressoras espalhadas em 3 Estados.

    Para coleta de dados, utilizamos muito jscript, porque precisamos de algumas validações.

    Se quiser trocar algumas ideias sobre, segue meu skype: marciliosi

    abraço!

    ResponderExcluir
  2. Muito bom o seu tutorial! parabéns!
    Poderia me dar uma ajudinha?

    Quando sigo este exemplo do seu select ;
    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

    **com as minhas variaveis e colunas, é claro rsrsrs**
    Para poder colocar no select @texto, se eu fizer por exemplo
    select @texto = 'teste de numero pedido' +(@nr_pedido)+ 'Fim do teste'
    ele imprime apenas a ultima linha da tabela apenas.muitas vezes na tabela tem

    pedido produto
    100 Prod_1
    100 Prod_2
    100 Prod_3

    ele grava no txt apenas a ultima linha da tabela " 100 Prod_3".. existe como contornar isso? pra ele imprimir por exemplo
    'teste de numero pedido' 100, produto_1 'Fim do teste
    'teste de numero pedido' 100, produto_2 'Fim do teste
    'teste de numero pedido' 100, produto_3 'Fim do teste

    valeu!

    ResponderExcluir