Cancelación aleatoria en la impresión de un Ticket

104 views
Skip to first unread message

Juan La Battaglia

unread,
Jul 22, 2014, 11:06:15 AM7/22/14
to pyfisca...@googlegroups.com
Arme usando pyFiscalPrinter un server en Flask que recibe comandos mediante javascript desde un browser (todo en la misma PC física).
Lo que está en el browser es una aplicación Rails, pero no viene al caso.

En la misma PC tengo instaladas una Hasar 320F y una Hasar 262F (es tickeadora térmica), ambas por USB con adaptador para puerto serie.

Funciona todo: en función al tipo de compra imprime: o bien factura (A o B) o bien ticket.

Mientras desarrollé, todas las pruebas me funcionaron bien, pero luego cuando el comercio empezó a usar "en serio"  algunos tickets salen cancelados.
La que falla es la impresora térmica Hasar modelo 262F, que más allá de la cantidad de columnas, es compatible con el modelo 615.

En general imprime bien, pero algunos tickets se cancelan a la mitad.
Luego si se imprime el mismo ticket, lo hace correctamente (no es error de datos).

¿Puede tener que ver la velocidad en la que envío los comandos de 'addItem'? lo hago mediante for sin ninguna espera.

El código es este:

	printer.openTicket()
	for item in document['items']:
		name = str(item['name'])
		quantity = item['quantity']
		amount = float(item['amount'])
		vat = float(item['vat'])			
		printer.addItem(name, quantity, amount, vat,  discount=0, discountDescription="")
¿Alguno tuvo esta experiencia?
Me cuesta determinar el error porque solo pasa muy de vez cuando.

Desde ya, muchas gracias.

Gracias

Juan La Battaglia

unread,
Jul 22, 2014, 12:13:04 PM7/22/14
to pyfisca...@googlegroups.com
Hago una aclaración sobre la pregunta anterior: Los tickets se cancelan porque el código está hecho como para que si hay una excepción, se cancele, así no queda un comprobante abierto.
Para menos vueltas, acá está el código de impresión de ticket/factura:

def printDocument(port, driver, model, document):
    res
= {'ok': False, 'response': {}}
    res
['response']['ticket_list_id'] = document['id']
   
try:
        printer
= getPrinter(port, driver, model)


       
if document['receipt_type'] in ['InvoiceB', 'InvoiceA']:
           
if document['receipt_type'] == 'InvoiceB':
                doc_type
= "B"
           
else:
                doc_type
= "A"
           
            docType
= printer.DOC_TYPE_CUIT
           
            ivaType_customer
= document['customer']['condition']        


           
if ivaType_customer == 'IvaResponsableInscripto':
                ivaType
= printer.IVA_TYPE_RESPONSABLE_INSCRIPTO    
           
elif ivaType_customer == 'IvaResponsableNoInscripto':
                ivaType
= printer.IVA_TYPE_RESPONSABLE_NO_INSCRIPTO
           
elif ivaType_customer == 'NoResponsable':
                ivaType
= printer.IVA_TYPE_NO_RESPONSABLE
           
elif ivaType_customer == 'IvaExento':
                ivaType
= printer.IVA_TYPE_EXENTO
           
elif ivaType_customer == 'ResponsableMonotributo':
                ivaType
= printer.IVA_TYPE_RESPONSABLE_MONOTRIBUTO
           
elif ivaType_customer == 'AConsumidorFinal':
                ivaType
= printer.IVA_TYPE_CONSUMIDOR_FINAL
                docType
= printer.DOC_TYPE_SIN_CALIFICADOR
           
else:
                ivaType
= printer.IVA_TYPE_NO_CATEGORIZADO


            document_number
= printer.getLastNumber(str(doc_type)) + 1


            name
= document['customer']['name']
            address
= document['customer']['address']
            number
= document['customer']['number']


            printer
.openBillTicket(str(doc_type), str(name), str(address), str(number), str(docType), str(ivaType), document['texts'])
       
elif document['receipt_type'] == 'Ticket':
            doc_type
= 'T'
           
#En la tickeadora no anda el setHeader
           
#printer.setHeader(document['texts'])
            printer
.openTicket()


       
else:
           
raise Exception('Tipo de factura invalido')


       
for item in document['items']:
            name
= str(item['name'])
            quantity
= item['quantity']
            amount
= float(item['amount'])
            vat
= float(item['vat'])            
            printer
.addItem(name, quantity, amount, vat,  discount=0, discountDescription="")


       
for payment in document['payments']:
           
for desc, value in payment.iteritems():
                printer
.addPayment(str(desc), float(value))


       
for discount in document['global_discounts']:
           
for desc, value in discount.iteritems():
                printer
.addAdditional(str(desc), value, 0, True)

        res_subtotal
= printer.getSubtotal()
        subtotal
= {}
        subtotal
['lines'] = res_subtotal[2]
        subtotal
['total'] = float(res_subtotal[3])
        subtotal
['vat'] = float(res_subtotal[4])
        subtotal
['canceled'] = 0
        subtotal
['invoice_letter'] = doc_type

        document_number
= printer.closeDocument()    

        subtotal
['document_number'] = document_number

        res
['ok'] = True    
        res
['response']['subtotal'] = subtotal
        res
['ok'] = True
   
except Exception as ex:
       
#ante cualquier error se cancela la factura
        printer
.cancelAnyDocument()
        res
['ok'] = False
        res
['error'] = str(ex)
        res
['response']['id'] = document['id']
        res
['response']['document_number'] = document_number
   
return res


Daniel

unread,
Jul 22, 2014, 2:26:28 PM7/22/14
to pyfisca...@googlegroups.com
El 22 de julio de 2014, 12:06, Juan La Battaglia <jua...@gmail.com> escribió:
Arme usando pyFiscalPrinter un server en Flask que recibe comandos mediante javascript desde un browser (todo en la misma PC física).
Lo que está en el browser es una aplicación Rails, pero no viene al caso.

En la misma PC tengo instaladas una Hasar 320F y una Hasar 262F (es tickeadora térmica), ambas por USB con adaptador para puerto serie.

Funciona todo: en función al tipo de compra imprime: o bien factura (A o B) o bien ticket.

Mientras desarrollé, todas las pruebas me funcionaron bien, pero luego cuando el comercio empezó a usar "en serio"  algunos tickets salen cancelados.
La que falla es la impresora térmica Hasar modelo 262F, que más allá de la cantidad de columnas, es compatible con el modelo 615.

En general imprime bien, pero algunos tickets se cancelan a la mitad.
Luego si se imprime el mismo ticket, lo hace correctamente (no es error de datos).

¿Puede tener que ver la velocidad en la que envío los comandos de 'addItem'? lo hago mediante for sin ninguna espera.

A mi me pasó algo similar con una epson, no cancelaba pero daba error de datos

y era la velocidad (en ciertas ocasiones no alcanzaba a procesar el comando y ponerse en ready antes de recibir el otro), probalo por las dudas
--
Daniel Malisani

Juan La Battaglia

unread,
Jul 22, 2014, 3:33:57 PM7/22/14
to pyfisca...@googlegroups.com
¿Y en ese sentido cómo lo manejaste? ¿poniendo un sleep en el lado del código? o hay forma de controlar si la impresora llega a ready.

Muchas gracias por la respuesta
 
--
Daniel Malisani

Guillermo M. Narvaja

unread,
Jul 23, 2014, 9:18:59 AM7/23/14
to pyfisca...@googlegroups.com

Lo que convendría hacer es lockear el acceso a la impresora, de forma tal que las comunicaciones sean secuenciales, enviar un comando recién cuando el anterior se dejó de ejecutar.
En el caso del socketserver que está actualmente eso se resuelve haciendo que el server atienda de a una conexión a la vez. Tendrías que ver cómo implementar algo similar en tu server flask.
Incluso tal vez no te alcance con lock por comando y tengas que implementar algo por operación, ya que si un cliente está a mitad de una factura, enviando items y otro quiere iniciar una nueva se va a armar quilombo. Te dejo a vos la creatividad de implementar eso en un protocolo sin conexión como http. Después compartí el resultado!!

Enviado con Aquamail para Android
http://www.aqua-mail.com

El 22 de julio de 2014 16:34:10 Juan La Battaglia <jua...@gmail.com> escribio:

--

---
Has recibido este mensaje porque estás suscrito al grupo "PyFiscalPrinter" de Grupos de Google.
Para anular la suscripción a este grupo y dejar de recibir sus mensajes, envía un correo electrónico a pyfiscalprint...@googlegroups.com.
Para acceder a más opciones, visita https://groups.google.com/d/optout.

Juan La Battaglia

unread,
Jul 23, 2014, 10:32:03 AM7/23/14
to pyfisca...@googlegroups.com
Por ahora el problema de la concurrencia de impresión de varias facturas no la tengo, porque el puesto que envía a imprimir es uno solo.
El problema por el que pregunto es más sencillo. ¿Tengo alguna confirmación por parte de la impresora para ir enviando los comandos que arman el ticket?

Puedo mandar un ticket varias veces a imprimir, pero alguna de esas veces falla.
Es decir: no es un error de datos, sino de comunicación.
¿No hay problema en mandar (por ejemplo) los addItem adentro de un for? ¿No tengo que esperar a que la impresora responda algo? ¿O eso lo maneja solo el driver?

Si llego a descubrir algo, lo comparto inmediatamente :-)

Gracias

Guillermo M. Narvaja

unread,
Jul 23, 2014, 10:44:19 AM7/23/14
to pyfisca...@googlegroups.com
El comando en el driver, envía y se queda esperando la respuesta, copio acá la porción de código:

101     def sendCommand( self, commandNumber, fields, skipStatusErrors = False ):
102         message = chr(0x02) + chr( self._sequenceNumber ) + chr(commandNumber)
103         if fields:
104             message += chr(0x1c)
105         message += chr(0x1c).join( fields )
106         message += chr(0x03)
107         checkSum = sum( [ord(x) for x in message ] )
108         checkSumHexa = ("0000" + hex(checkSum)[2:])[-4:].upper()
109         message += checkSumHexa
110         reply = self._sendMessage( message )
111         self._incrementSequenceNumber()
112         return self._parseReply( reply, skipStatusErrors )
113

El tema es que si vos estás haciendo "addItem" a un servidor HTTP multihilo, no tenés garantizado que antes de empiece a procesar un nuevo comando se haya terminado el anterior. Si el server HTTP NO fuera multithread, si procesara solo un request HTTP a la vez, creo que no deberías tener problema en hacer, como decís un simple for.
-- 
Guillermo M. Narvaja
Lambda Sistemas S.R.L.
www.fierro-soft.com.ar - radiocut.fm
Tel: (5411) 3220-1520 (rotativas) y 4857-6662
Cel: (5411) 15-6783-4435
Email: guillerm...@fierro-soft.com.ar
MSN: guillerm...@hotmail.com
Skype: guillermonarvaja
Lavalleja 519 1er Piso - Ciudad de Buenos Aires - Argentina

Juan La Battaglia

unread,
Jul 23, 2014, 11:15:31 AM7/23/14
to pyfisca...@googlegroups.com, guillerm...@fierro-soft.com.ar
Entonces voy a tener que ir más a fondo, porque al servidor le llegan los datos completos del ticket (un json con todo lo necesario para hacer un ticket)
Y una vez con todos esos datos, ahí le empieza a hablar a la impresora. Es solo un request http, no hay mutithread.

Si el driver espera por la respuesta, voy a tener que evaluar exactamente cuál es el problema en la impresora viendo si puedo tomar alguna información de la excepción que se levanta.

Gracias

Juan La Battaglia

unread,
Jul 28, 2014, 1:40:58 PM7/28/14
to pyfisca...@googlegroups.com, guillerm...@fierro-soft.com.ar
Les cuento que tenía dos problemas para la cancelación, y pude resolver los dos.
Ninguna de las dos es muy elegante, pero los errores que recibía tampoco lo eran:

1) Problema: La impresora respondía a algún addItem como "Operación no válida para el estado fiscal", (cosa que no era cierta ya que envío los comandos en orden)
Solución: Al capturar esta excepción, cancelo el ticket y lo reimprimo. El resultado es que sale un ticket cancelado e inmediatamente uno correcto.

2) Problema: La impresora NO responde al enviar addItem, ni siquera después de los 10 segundos de time out que tiene configurado el driver, es decir: se quedaba colgado.
Solución: encontré que mientras está en ese estado, si abría una consola y tiraba un comando a la impresora, por ejemplo ejecutando el test.py la impresora respondía error, y el proceso que estaba trabado se destrababa con una excepción de error de lectura del puerto.
Lo que hago entonces es usar un thread en pyton para ejecutar un comando de consulta de estatus en la impresora  si llegan a pasar mas de 3 segundo de enviado un comando cualquiera. Con eso captura la excepción en el flujo normal, cancelo el ticket actual y re imprimo (como la solución anterior)

NOTA: Los errores aleatorios se pueden llegar a deber a que uso una impresora Hasar 262F que ya es muy vieja y tenga algún problema de velocidad en la respuesta.

Dejo una muestra del código de desbloqueo por tiempo.

Muchas gracias

import threading


def unlock_by_time(printer):
   
try:
       
print 'unlock_by_time'
        printer
.getLastNumber("B") #es solo para preguntarle algo
       
print 'unlock_by_time OK'
   
except Exception as e:
       
print 'Exception en unlock_by_time'
       
print 'Exception: ' + str(e)


def unlock_by_time_wrapper(printer, function_name):
    t
= threading.Timer(3, unlock_by_time, [printer]) #ejecuta si pasan mas de 3 segundos
    t
.start() #empieza a contar
    printer
'Start thread for unlock_by_time for: ' + function_name
   
yield    
    t
.cancel() #si llega hasta acá no se ejecuta el thread
   
print 'Cancel thread for unlock_by_time for: ' + function_name


def imprimir_ticket():
   
[...]
   
print 'send addItem'
   
for x in unlock_by_time_wrapper(printer, 'addItem'): #ejecución de bloque

        printer
.addItem(name, quantity, amount, vat,  discount=0, discountDescription="")

   
print 'send addItem OK'
   
[...]

Reply all
Reply to author
Forward
0 new messages