Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,6 @@ nosetests.xml
.mr.developer.cfg
.project
.pydevproject


.idea/
4 changes: 0 additions & 4 deletions README

This file was deleted.

19 changes: 19 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,22 @@ pyfiscalprinter
===============

Drivers for invoice/tickets fiscal printers (Epson & Hasar) Argentina


files description
-----------------

* driver.py: it manage communication protocol
* generic.py: it's an abstract interface for printer implementation
* epson.py: implementation for Epson's printers
* hasar.py: implementation for Hasar's printers


TODO
----

* Improve documentation
* PEP8
* make python3 compatible
* code cleanup
* Unit tests
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion __init__.py → pyfiscalprinter/__init__.py
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,4 @@
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

__version__ = "1.0"
__version__ = "1.0"
17 changes: 9 additions & 8 deletions agente.py → pyfiscalprinter/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
import time
import os

from controlador import ControladorFiscal
from controller import PyFiscalPrinter

# ejemplo para http://localhost:8000 :

Expand Down Expand Up @@ -54,33 +54,34 @@
</html>
"""


class Handler(BaseHTTPRequestHandler):
def do_GET(self):
if not self.path.startswith(("/", "/hola.js")):
self.send_error(404, "File not found")
else:
if self.path == "/":
# muestro la página de ejemplo
# muestro la pagina de ejemplo
content = INDEX
content_type = "text/html"
elif '?' in self.path:
# analizo la URL, extrayendo las variables:
path, tmp = self.path.split('?', 1)
method, ext = os.path.splitext(path[1:])
qs = cgi.parse_qs(tmp)
# obtengo la función a llamar
# obtengo la funcion a llamar
fn = getattr(self.server.controlador, method)
callback = qs['callback'].pop()
kwargs = dict([(k, v[0]) for k, v in qs.items() if v])
# ejecuto el método del controlador
# ejecuto el metodo del controlador
ret = fn(**kwargs)
# reviso el valor devuelto (limpio si es excepción):
# reviso el valor devuelto (limpio si es excepcion):
ex = self.server.controlador.Excepcion
if ex:
ex = ex.replace('"', "").replace("'", "").replace("\n", "")
if isinstance(ex, str):
ex = ex.decode("ascii", "replace")
# devuelvo JS que muestre la excepción:
# devuelvo JS que muestre la excepcion:
content = """alert("%s");""" % (ex.encode("ascii", "replace"))
else:
if ret is None:
Expand All @@ -93,7 +94,7 @@ def do_GET(self):
# prueba simple:
content_type = "application/javascript"
content = """alert("hola mundo %s!");""" % time.time()
# envío la respuesta (no cachear y compatibilidad con IE 8):
# envio la respuesta (no cachear y compatibilidad con IE 8):
self.send_response(200)
self.send_header("Content-type", content_type)
self.send_header("X-UA-Compatible", "IE=8")
Expand All @@ -105,6 +106,6 @@ def do_GET(self):

# levanto el servidor local:
server = HTTPServer(("localhost", 8000), Handler)
server.controlador = ControladorFiscal()
server.controller = PyFiscalPrinter()
server.serve_forever()

136 changes: 73 additions & 63 deletions controlador.py → pyfiscalprinter/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
http://www.sistemasagiles.com.ar/trac/wiki/PyFiscalPrinter
"""

AYUDA=u"""
AYUDA = u"""
Opciones:
--ayuda: este mensaje
--licencia: muestra la licencia del programa
Expand Down Expand Up @@ -61,9 +61,8 @@

# Drivers:

from epsonFiscal import EpsonPrinter
from hasarPrinter import HasarPrinter

from epson import EpsonPrinter
from hasar import HasarPrinter

try:
import dbus, dbus.mainloop.glib
Expand All @@ -77,37 +76,39 @@

def inicializar_y_capturar_excepciones(func):
"Decorador para inicializar y capturar errores"

@wraps(func)
def capturar_errores_wrapper(self, *args, **kwargs):
try:
# inicializo (limpio variables)
self.Traceback = self.Excepcion = ""
return func(self, *args, **kwargs)
except Exception, e:
ex = traceback.format_exception( sys.exc_type, sys.exc_value, sys.exc_traceback)
ex = traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback)
self.Traceback = ''.join(ex)
self.Excepcion = traceback.format_exception_only( sys.exc_type, sys.exc_value)[0]
self.Excepcion = traceback.format_exception_only(sys.exc_type, sys.exc_value)[0]
if self.LanzarExcepciones:
raise
finally:
pass

return capturar_errores_wrapper


class PyFiscalPrinter(Object):
"Interfaz unificada para imprimir facturas en controladores fiscales"
_public_methods_ = ['Conectar',
'AbrirComprobante', 'CerrarComprobante',
'ImprimirItem', 'ImprimirPago',
'ImprimirItem', 'ImprimirPago',
'ConsultarUltNro',
]
_public_attrs_ = ['Version', 'Excepcion', 'Traceback', 'LanzarExcepciones',
]
]

_reg_progid_ = "PyFiscalPrinter"
_reg_clsid_ = "{4E214B11-424E-40F7-9869-680C9520125E}"
DBUS_IFACE = 'ar.com.pyfiscalprinter.Interface'

def __init__(self, session_bus=None, object_path=None):
if dbus:
Object.__init__(self, session_bus, object_path)
Expand Down Expand Up @@ -136,42 +137,42 @@ def Conectar(self, marca="epson", modelo="320", puerto="COM1", equipo=None):
printer = Printer(model=modelo, host=equipo, port=int(puerto), dummy=dummy)
self.printer = printer
self.cbte_fiscal_map = {
1: 'FA', 2: 'NDA', 3: 'NCA',
6: 'FB', 7: 'NDB', 8: 'NCB',
11: 'FC', 12: 'NDC', 13: 'NDC',
81: 'FA', 82: 'FB', 83: 'T', # tiques
}
1: 'FA', 2: 'NDA', 3: 'NCA',
6: 'FB', 7: 'NDB', 8: 'NCB',
11: 'FC', 12: 'NDC', 13: 'NDC',
81: 'FA', 82: 'FB', 83: 'T', # tiques
}
self.pos_fiscal_map = {
1: printer.IVA_TYPE_RESPONSABLE_INSCRIPTO,
2: printer.IVA_TYPE_RESPONSABLE_NO_INSCRIPTO,
3: printer.IVA_TYPE_NO_RESPONSABLE,
4: printer.IVA_TYPE_EXENTO,
5: printer.IVA_TYPE_CONSUMIDOR_FINAL,
6: printer.IVA_TYPE_RESPONSABLE_MONOTRIBUTO,
7: printer.IVA_TYPE_NO_CATEGORIZADO,
12: printer.IVA_TYPE_PEQUENIO_CONTRIBUYENTE_EVENTUAL,
13: printer.IVA_TYPE_MONOTRIBUTISTA_SOCIAL,
14: printer.IVA_TYPE_PEQUENIO_CONTRIBUYENTE_EVENTUAL_SOCIAL,
}
1: printer.IVA_TYPE_RESPONSABLE_INSCRIPTO,
2: printer.IVA_TYPE_RESPONSABLE_NO_INSCRIPTO,
3: printer.IVA_TYPE_NO_RESPONSABLE,
4: printer.IVA_TYPE_EXENTO,
5: printer.IVA_TYPE_CONSUMIDOR_FINAL,
6: printer.IVA_TYPE_RESPONSABLE_MONOTRIBUTO,
7: printer.IVA_TYPE_NO_CATEGORIZADO,
12: printer.IVA_TYPE_PEQUENIO_CONTRIBUYENTE_EVENTUAL,
13: printer.IVA_TYPE_MONOTRIBUTISTA_SOCIAL,
14: printer.IVA_TYPE_PEQUENIO_CONTRIBUYENTE_EVENTUAL_SOCIAL,
}
self.doc_fiscal_map = {
96: printer.DOC_TYPE_DNI,
80: printer.DOC_TYPE_CUIT,
89: printer.DOC_TYPE_LIBRETA_ENROLAMIENTO,
90: printer.DOC_TYPE_LIBRETA_CIVICA,
00: printer.DOC_TYPE_CEDULA,
94: printer.DOC_TYPE_PASAPORTE,
99: printer.DOC_TYPE_SIN_CALIFICADOR,
}
96: printer.DOC_TYPE_DNI,
80: printer.DOC_TYPE_CUIT,
89: printer.DOC_TYPE_LIBRETA_ENROLAMIENTO,
90: printer.DOC_TYPE_LIBRETA_CIVICA,
00: printer.DOC_TYPE_CEDULA,
94: printer.DOC_TYPE_PASAPORTE,
99: printer.DOC_TYPE_SIN_CALIFICADOR,
}
return True

def DebugLog(self):
"Devolver bitácora de depuración"
msg = self.log.getvalue()
return msg
return msg

@inicializar_y_capturar_excepciones
@method(DBUS_IFACE, in_signature='iiissss', out_signature='b')
def AbrirComprobante(self,
def AbrirComprobante(self,
tipo_cbte=83, # tique
tipo_responsable=5, # consumidor final
tipo_doc=99, nro_doc=0, # sin especificar
Expand All @@ -184,7 +185,7 @@ def AbrirComprobante(self,
self.factura = {"encabezado": dict(tipo_cbte=tipo_cbte,
tipo_responsable=tipo_responsable,
tipo_doc=tipo_doc, nro_doc=nro_doc,
nombre_cliente=nombre_cliente,
nombre_cliente=nombre_cliente,
domicilio_cliente=domicilio_cliente,
referencia=referencia),
"items": [], "pagos": []}
Expand All @@ -205,29 +206,30 @@ def AbrirComprobante(self,
else:
ret = printer.openTicket()
elif cbte_fiscal.startswith("F"):
ret = printer.openBillTicket(letra_cbte, nombre_cliente, domicilio_cliente,
ret = printer.openBillTicket(letra_cbte, nombre_cliente, domicilio_cliente,
nro_doc, doc_fiscal, pos_fiscal)
elif cbte_fiscal.startswith("ND"):
ret = printer.openDebitNoteTicket(letra_cbte, nombre_cliente,
domicilio_cliente, nro_doc, doc_fiscal,
ret = printer.openDebitNoteTicket(letra_cbte, nombre_cliente,
domicilio_cliente, nro_doc, doc_fiscal,
pos_fiscal)
elif cbte_fiscal.startswith("NC"):
ret = printer.openBillCreditTicket(letra_cbte, nombre_cliente,
domicilio_cliente, nro_doc, doc_fiscal,
ret = printer.openBillCreditTicket(letra_cbte, nombre_cliente,
domicilio_cliente, nro_doc, doc_fiscal,
pos_fiscal, referencia)
return True

@inicializar_y_capturar_excepciones
@method(DBUS_IFACE, in_signature='vvvv', out_signature='b')
def ImprimirItem(self, ds, qty, importe, alic_iva=21.):
def ImprimirItem(self, ds, qty, importe, alic_iva=21., descripcion_larga=False, redondeo_hacia_arriba=False):
"Envia un item (descripcion, cantidad, etc.) a una factura"
self.factura["items"].append(dict(ds=ds, qty=qty,
self.factura["items"].append(dict(ds=ds, qty=qty,
importe=importe, alic_iva=alic_iva))
##ds = unicode(ds, "latin1") # convierto a latin1
# Nota: no se calcula neto, iva, etc (deben venir calculados!)
discount = discountDescription = None
self.printer.addItem(ds, float(qty), float(importe), float(alic_iva),
discount, discountDescription)
discount = discountDescription = None
negative = False
self.printer.addItem(ds, float(qty), float(importe), float(alic_iva),
discount, discountDescription, negative, descripcion_larga, redondeo_hacia_arriba)
return True

@inicializar_y_capturar_excepciones
Expand All @@ -254,11 +256,18 @@ def ConsultarUltNro(self, tipo_cbte):
letra_cbte = cbte_fiscal[-1] if len(cbte_fiscal) > 1 else None
return self.printer.getLastNumber(letra_cbte)

@inicializar_y_capturar_excepciones
@method(DBUS_IFACE, in_signature='', out_signature='')
def AgregarAdicional(self, descripcion, monto, iva, negativo=False):
"Agrega un adicional o Bonificacion"
return self.printer.addAdditional(descripcion, monto, iva, negativo)


if __name__ == '__main__':

if "--register" in sys.argv or "--unregister" in sys.argv:
import win32com.server.register

win32com.server.register.UseCommandLine(PyFiscalPrinter)
elif "--dbus" in sys.argv:
dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
Expand All @@ -272,12 +281,12 @@ def ConsultarUltNro(self, tipo_cbte):
from ConfigParser import SafeConfigParser

DEBUG = '--debug' in sys.argv

# leeo configuración (primer argumento o rece.ini por defecto)
if len(sys.argv)>1 and not sys.argv[1].startswith("--"):
if len(sys.argv) > 1 and not sys.argv[1].startswith("--"):
CONFIG_FILE = sys.argv.pop(1)
if DEBUG: print "CONFIG_FILE:", CONFIG_FILE

config = SafeConfigParser()
config.read(CONFIG_FILE)
if config.has_section('CONTROLADOR'):
Expand All @@ -292,7 +301,7 @@ def ConsultarUltNro(self, tipo_cbte):
if '--licencia' in sys.argv:
print LICENCIA
sys.exit(0)

controlador = PyFiscalPrinter()
controlador.LanzarExcepciones = True

Expand All @@ -305,27 +314,28 @@ def ConsultarUltNro(self, tipo_cbte):
if '--ult' in sys.argv:
print "Consultar ultimo numero:"
i = sys.argv.index("--ult")
if i+1 < len(sys.argv):
tipo_cbte = int(sys.argv[i+1])
if i + 1 < len(sys.argv):
tipo_cbte = int(sys.argv[i + 1])
else:
tipo_cbte = int(raw_input("Tipo de comprobante: ") or 83)
tipo_cbte = int(raw_input("Tipo de comprobante: ") or 83)
ult = controlador.ConsultarUltNro(tipo_cbte)
print "Ultimo Nro de Cbte:", ult

if '--prueba' in sys.argv:
# creo una factura de ejemplo
tipo_cbte = 6
tipo_doc = 80; nro_doc = "20267565393"
tipo_doc = 80;
nro_doc = "20267565393"
nombre_cliente = 'Joao Da Silva'
domicilio_cliente = 'Rua 76 km 34.5 Alagoas'
tipo_responsable = 5
referencia = None
ok = controlador.AbrirComprobante(tipo_cbte, tipo_responsable,
tipo_doc, nro_doc,
nombre_cliente, domicilio_cliente,
referencia)

ok = controlador.AbrirComprobante(tipo_cbte, tipo_responsable,
tipo_doc, nro_doc,
nombre_cliente, domicilio_cliente,
referencia)

codigo = "P0001"
ds = "Descripcion del producto P0001"
qty = 1.00
Expand All @@ -337,9 +347,9 @@ def ConsultarUltNro(self, tipo_cbte):

ok = controlador.ImprimirPago("efectivo", importe)
ok = controlador.CerrarComprobante()

with open(conf.get("entrada", "factura.json"), "w") as f:
json.dump(controlador.factura, f,
json.dump(controlador.factura, f,
indent=4, separators=(',', ': '))

else:
Expand Down
File renamed without changes.
Loading