├── .hgtags ├── .gitignore ├── datos.py ├── README.md ├── setup.py ├── consultas.py ├── LICENSE └── factura.pyw /.hgtags: -------------------------------------------------------------------------------- 1 | c996410068b91ac1cb0219783363d7e6693f9542 0.9d 2 | 79968be49e0f757266ad12b66ba6a0cf613ae90a 0.9g 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov/ 31 | .tox/ 32 | .coverage 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | 37 | # Translations 38 | *.mo 39 | 40 | # Mr Developer 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | 45 | # Rope 46 | .ropeproject 47 | 48 | # Django stuff: 49 | *.log 50 | *.pot 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | -------------------------------------------------------------------------------- /datos.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf8 -*- 3 | 4 | articulos = { 5 | '1': u'PRODUCTO 1', 6 | '2': u'PRODUCTO 2', 7 | } 8 | 9 | TIPO_DOC_MAP= {80: u'CUIT', 96: u'DNI', 99: u'CF', 10 | 91: "CI Extranjera", 94: "Pasaporte"} 11 | 12 | TIPO_CBTE_MAP = {1: u'Factura A', 2: u'Nota de Débito A', 13 | 3: u'Nota de Crédito A', 4: 'Recibo A', 14 | 6: u'Factura B', 7: u'Nota de Débito B', 15 | 8: u'Nota de Crédito B', 9: 'Recibo B', 16 | 11: u'Factura C', 12: u'Nota de Débito C', 17 | 13: u'Nota de Crédito C', 15: 'Recibo C', 18 | 51: u'Factura M', 52: u'Nota de Débito M', 19 | 53: u'Nota de Crédito M', 54: u'Recibo M', 20 | } 21 | 22 | CLASE_C = 11, 12, 13, 15 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | pyfactura 2 | ========= 3 | 4 | Visual application for electronic invoices (AFIP Argentina) 5 | 6 | Copyright 2014 by Mariano Reingart 7 | 8 | Licenced under GPLv3+ 9 | 10 | Documentation: http://www.sistemasagiles.com.ar/trac/wiki/PyFactura (spanish) 11 | 12 | 13 | ![screenshot](http://www.sistemasagiles.com.ar/trac/raw-attachment/wiki/PyFactura/aplicativo_factura_electronica_06b_ubuntu.png) 14 | 15 | Features: 16 | --------- 17 | 18 | * Simple form to enter electronic invoice data (customer, dates, items, taxes) 19 | * Online autorization of the infoice agains AFIP (Argentina's Federal Tax Agency) 20 | * PDF generation customizable with extra data, logo, barcode, etc. 21 | * Email sending with PDF attachment 22 | * Internal database (sqlite embedded, compatible with PostgreSQL, MySQL or ODBC -MSSQL Server and MS Access-) 23 | 24 | Configuration: 25 | -------------- 26 | 27 | rece.ini configuration example: 28 | 29 | [WSAA] 30 | CERT=reingart.crt 31 | PRIVATEKEY=reingart.key 32 | ##URL=https://wsaa.afip.gov.ar/ws/services/LoginCms 33 | 34 | [WSFEv1] 35 | CUIT=20267565393 36 | CAT_IVA=1 37 | PTO_VTA=97 38 | ENTRADA=entrada.txt 39 | SALIDA=salida.txt 40 | ##URL=https://servicios1.afip.gov.ar/wsfev1/service.asmx?WSDL 41 | 42 | [FACTURA] 43 | ARCHIVO=tipo,letra,numero 44 | FORMATO=factura.csv 45 | DIRECTORIO=. 46 | PAPEL=legal 47 | ORIENTACION=portrait 48 | DIRECTORIO=. 49 | SUBDIRECTORIO= 50 | LOCALE=Spanish_Argentina.1252 51 | FMT_CANTIDAD=0.4 52 | FMT_PRECIO=0.3 53 | CANT_POS=izq 54 | ENTRADA=factura.txt 55 | SALIDA=factura.pdf 56 | 57 | [PDF] 58 | LOGO=logo.png 59 | EMPRESA=Empresa de Prueba 60 | MEMBRETE1=Direccion de Prueba 61 | MEMBRETE2=Capital Federal 62 | CUIT=CUIT 30-00000000-0 63 | IIBB=IIBB 30-00000000-0 64 | IVA=IVA Responsable Inscripto 65 | INICIO=Inicio de Actividad: 01/04/2006 66 | 67 | [MAIL] 68 | SERVIDOR=adan.nsis.com.ar 69 | PUERTO=25 70 | USUARIO=no.responder@nsis.com.ar 71 | CLAVE=noreplyauto123 72 | MOTIVO=Factura Electronica Nro. NUMERO 73 | CUERPO=Se adjunta Factura en formato PDF 74 | HTML=Se adjunta factura electronica en formato PDF 75 | REMITENTE=Facturador PyAfipWs 76 | 77 | 78 | See User Manual: http://www.sistemasagiles.com.ar/trac/wiki/ManualPyAfipWs for more information 79 | 80 | 81 | Requeriments: 82 | ------------- 83 | 84 | * Programming language: Python 2.5+ 85 | * GUI toolkit: wxPython 2.8+ 86 | * GUI framework: [gui2py](https://code.google.com/p/gui2py/) (pythoncard fork/spin-off) 87 | * Electronic invoice components: [pyafipws](https://code.google.com/p/pyafipws/) 88 | * SOAP Webservices lightweight library: [pysimplesoap](https://code.google.com/p/pysimplesoap/) 89 | * PDF generation lightweight library: [pyfpdf](https://code.google.com/p/pyfpdf/) 90 | 91 | Detailed build and instalation instructions in https://code.google.com/p/pyafipws/wiki/InstalacionCodigoFuente 92 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: latin-1 -*- 3 | 4 | # Para hacer el ejecutable: 5 | # python setup.py py2exe 6 | # 7 | 8 | "Creador de instalador para PyFactura" 9 | 10 | __author__ = "Mariano Reingart (reingart@gmail.com)" 11 | __copyright__ = "Copyright (C) 2014 Mariano Reingart" 12 | 13 | factura = __import__("factura") 14 | __version__ = factura.__version__ 15 | 16 | from distutils.core import setup 17 | import glob 18 | import os 19 | import sys 20 | 21 | 22 | # parametros para setup: 23 | kwargs = {} 24 | 25 | long_desc = "Aplicativo visual para generación Facturas Electrónicas AFIP" 26 | 27 | 28 | data_files = [ 29 | (".", ["licencia.txt", "sistemas-agiles.png", "logo-pyafipws.png"]), 30 | ("conf", ["conf/rece.ini", "conf/geotrust.crt", "conf/afip_ca_info.crt", ]), 31 | ("cache", glob.glob("cache/*")), 32 | ] 33 | 34 | if os.path.exists("reingart.crt"): 35 | data_files.append(("conf", ["reingart.crt", "reingart.key"])) 36 | 37 | HOMO = False 38 | 39 | # build a one-click-installer for windows: 40 | if 'py2exe' in sys.argv: 41 | import py2exe 42 | from pyafipws.nsis import build_installer, Target 43 | 44 | # includes for py2exe 45 | includes=['email.generator', 'email.iterators', 'email.message', 'email.utils', 'email.mime.text', 'email.mime.application', 'email.mime.multipart'] 46 | 47 | # optional modules: 48 | # required modules for shelve support (not detected by py2exe by default): 49 | for mod in ['socks', 'dbhash', 'gdbm', 'dbm', 'dumbdbm', 'anydbm']: 50 | try: 51 | __import__(mod) 52 | includes.append(mod) 53 | except ImportError: 54 | pass 55 | 56 | # don't pull in all this MFC stuff used by the makepy UI. 57 | excludes=["pywin", "pywin.dialogs", "pywin.dialogs.list", "win32ui", 58 | "Tkconstants","Tkinter","tcl", 59 | "_imagingtk", "PIL._imagingtk", "ImageTk", "PIL.ImageTk", "FixTk", 60 | ] 61 | 62 | # basic options for py2exe 63 | opts = { 64 | 'py2exe': { 65 | 'includes': includes, 66 | 'optimize': 0, 67 | 'excludes': excludes, 68 | 'dll_excludes': ["mswsock.dll", "powrprof.dll", "KERNELBASE.dll", 69 | "API-MS-Win-Core-LocalRegistry-L1-1-0.dll", 70 | "API-MS-Win-Core-ProcessThreads-L1-1-0.dll", 71 | "API-MS-Win-Security-Base-L1-1-0.dll", 72 | "api-ms-win-core-delayload-l1-1-1.dll", 73 | "api-ms-win-core-errorhandling-l1-1-1.dll", 74 | "api-ms-win-core-handle-l1-1-0.dll", 75 | "api-ms-win-core-heap-l1-2-0.dll", 76 | "api-ms-win-core-heap-obsolete-l1-1-0.dll", 77 | "api-ms-win-core-libraryloader-l1-2-0.dll", 78 | "api-ms-win-core-localization-obsolete-l1-2-0.dll", 79 | "api-ms-win-core-processthreads-l1-1-2.dll", 80 | "api-ms-win-core-profile-l1-1-0.dll", 81 | "api-ms-win-core-registry-l1-1-0.dll", 82 | "api-ms-win-core-string-l1-1-0.dll", 83 | "api-ms-win-core-string-obsolete-l1-1-0.dll", 84 | "api-ms-win-core-synch-l1-2-0.dll", 85 | "api-ms-win-core-sysinfo-l1-2-1.dll", 86 | "api-ms-win-security-base-l1-2-0.dll", 87 | "crypt32.dll", "WLDAP32.dll", 88 | ], 89 | 'skip_archive': True, 90 | } 91 | } 92 | 93 | desc = "Instalador PyAfipWs" 94 | kwargs['com_server'] = [] 95 | kwargs['console'] = [] 96 | kwargs['windows'] = [] 97 | 98 | # add 32bit or 64bit tag to the installer name 99 | import platform 100 | __version__ += "-" + platform.architecture()[0] 101 | 102 | # visual application 103 | # find pythoncard resources, to add as 'data_files' 104 | pycard_resources=[] 105 | for filename in os.listdir('.'): 106 | if filename.find('.rsrc.')>-1: 107 | pycard_resources+=[filename] 108 | 109 | kwargs['console'] += [ 110 | Target(module=factura, script="factura.pyw", dest_base="factura_consola"), 111 | ] 112 | kwargs['windows'] += [ 113 | Target(module=factura, script='factura.pyw'), 114 | ] 115 | data_files += [ 116 | ("plantillas", ["plantillas/logo.png", "plantillas/afip.png", "plantillas/factura.csv", "plantillas/recibo.csv",]), 117 | ("cache", glob.glob("cache/*")), 118 | #("datos", ["datos/facturas.csv", "datos/facturas.json", "datos/facturas.txt", ]) 119 | (".", [ 120 | "sistemas-agiles.png", "logo-pyafipws.png", "reingart.key", "reingart.crt", 121 | "pyafipws/padron.db", 122 | ]) 123 | ] 124 | data_files.append((".", pycard_resources)) 125 | 126 | try: 127 | import designer 128 | kwargs['windows'] += [ 129 | Target(module=designer, script="designer.py", dest_base="designer"), 130 | ] 131 | except ImportError: 132 | # el script pyfpdf/tools/designer.py no esta disponible: 133 | print "IMPORTANTE: no se incluye el diseñador de plantillas PDF" 134 | 135 | 136 | # add certification authorities (newer versions of httplib2) 137 | try: 138 | import httplib2 139 | if httplib2.__version__ >= "0.9": 140 | data_files += [("httplib2", 141 | [os.path.join(os.path.dirname(httplib2.__file__), "cacerts.txt")])] 142 | except ImportError: 143 | pass 144 | 145 | # custom installer: 146 | kwargs['cmdclass'] = {"py2exe": build_installer} 147 | 148 | if sys.version_info > (2, 7): 149 | # add "Microsoft Visual C++ 2008 Redistributable Package (x86)" 150 | if os.path.exists(r"c:\Program Files\Mercurial"): 151 | data_files += [( 152 | ".", glob.glob(r'c:\Program Files\Mercurial\msvc*.dll') + 153 | glob.glob(r'c:\Program Files\Mercurial\Microsoft.VC90.CRT.manifest'), 154 | )] 155 | sys.path.insert(0, r"C:\Python27\Lib\site-packages\pythonwin") 156 | data_files += [( 157 | ".", glob.glob(r'C:\Python27\Lib\site-packages\pythonwin\mfc*.*') + 158 | glob.glob(r'C:\Python27\Lib\site-packages\pythonwin\Microsoft.VC90.MFC.manifest'), 159 | )] 160 | else: 161 | data_files += [(".", [ 162 | "C:\python25\Lib\site-packages\wx-2.8-msw-unicode\wx\MSVCP71.dll", 163 | "C:\python25\MSVCR71.dll", 164 | "C:\python25\lib\site-packages\wx-2.8-msw-unicode\wx\gdiplus.dll", 165 | ])] 166 | 167 | # agrego tag de homologación (testing - modo evaluación): 168 | __version__ += "-homo" if HOMO else "-full" 169 | 170 | else: 171 | desc = "Paquete PyFactura" 172 | kwargs['package_dir'] = {'pyafipws': '.'} 173 | kwargs['packages'] = ['pyafipws'] 174 | opts = {} 175 | 176 | 177 | setup(name="PyFactura", 178 | version=__version__, 179 | description=desc, 180 | long_description=long_desc, 181 | author="Mariano Reingart", 182 | author_email="reingart@gmail.com", 183 | url="https://code.google.com/p/pyafipws/" if 'register' in sys.argv 184 | else "http://www.sistemasagiles.com.ar", 185 | license="GNU GPL v3", 186 | options=opts, 187 | data_files=data_files, 188 | classifiers = [ 189 | "Development Status :: 5 - Production/Stable", 190 | "Intended Audience :: Developers", 191 | "Intended Audience :: End Users/Desktop", 192 | "Intended Audience :: Financial and Insurance Industry", 193 | "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", 194 | "Programming Language :: Python", 195 | "Programming Language :: Python :: 2.5", 196 | "Programming Language :: Python :: 2.6", 197 | "Programming Language :: Python :: 2.7", 198 | #"Programming Language :: Python :: 3.2", 199 | "Operating System :: OS Independent", 200 | "Operating System :: Microsoft :: Windows", 201 | "Natural Language :: Spanish", 202 | "Topic :: Office/Business :: Financial :: Point-Of-Sale", 203 | "Topic :: Software Development :: Libraries :: Python Modules", 204 | "Topic :: Software Development :: Object Brokering", 205 | ], 206 | keywords="webservice electronic invoice pdf traceability", 207 | **kwargs 208 | ) 209 | 210 | -------------------------------------------------------------------------------- /consultas.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | "Aplicativo Factura Electronica Libre" 5 | 6 | from __future__ import with_statement # for python 2.5 compatibility 7 | 8 | __author__ = "Mariano Reingart (reingart@gmail.com)" 9 | __copyright__ = "Copyright (C) 2015- Mariano Reingart" 10 | __license__ = "GPL 3.0+" 11 | __version__ = "0.7c" 12 | 13 | # Documentación: http://www.sistemasagiles.com.ar/trac/wiki/PyFactura 14 | 15 | import calendar 16 | import datetime 17 | import decimal 18 | import os 19 | import time 20 | import traceback 21 | import sys 22 | from ConfigParser import SafeConfigParser 23 | 24 | import gui # import gui2py package (shortcuts) 25 | 26 | from pyafipws.padron import PadronAFIP 27 | from pyafipws.sired import SIRED 28 | from pyafipws.wsaa import WSAA 29 | from pyafipws.wsfev1 import WSFEv1 30 | from pyafipws.pyfepdf import FEPDF 31 | from pyafipws.pyemail import PyEmail 32 | 33 | # set default locale to handle correctly numeric format (maskedit): 34 | import wx, locale 35 | if sys.platform == "win32": 36 | locale.setlocale(locale.LC_ALL, 'Spanish_Argentina.1252') 37 | elif sys.platform == "linux2": 38 | locale.setlocale(locale.LC_ALL, 'es_AR.utf8') 39 | loc = wx.Locale(wx.LANGUAGE_DEFAULT, wx.LOCALE_LOAD_DEFAULT) 40 | 41 | CONFIG_FILE = "rece.ini" 42 | HOMO = WSAA.HOMO or WSFEv1.HOMO 43 | import datos 44 | 45 | 46 | # --- here go your event handlers --- 47 | 48 | def on_tipo_doc_change(evt): 49 | ctrl = evt.target 50 | value = "" 51 | if ctrl.value == 80: 52 | mask = '##-########-#' 53 | elif ctrl.value == 99: 54 | mask = '#' 55 | value = "0" 56 | #on_nro_doc_change(evt) 57 | else: 58 | mask = '########' 59 | panel['criterios']['nro_doc'].mask = mask 60 | panel['criterios']['nro_doc'].value = value 61 | 62 | 63 | def buscar(evt, fn=SIRED().Consultar): 64 | listado = mywin['panel']['listado'] 65 | listado.items.clear() 66 | 67 | # obtengo criterio de busqueda: 68 | tipo_doc = panel['criterios']['tipo_doc'].value 69 | nro_doc = panel['criterios']['nro_doc'].value.replace("-", "").strip() 70 | nro_doc = int(nro_doc) if nro_doc else None 71 | nombre_cliente = panel['criterios']['nombre_cliente'].value 72 | tipo_cbte = panel['criterios']['tipo_cbte'].value 73 | pto_vta = panel['criterios']['pto_vta'].value 74 | nro_cbte_desde = panel['criterios']['nro_cbte_desde'].value 75 | nro_cbte_hasta = panel['criterios']['nro_cbte_hasta'].value 76 | fecha_cbte_hasta = panel['criterios']['fecha_cbte_hasta'].value 77 | fecha_cbte_desde = panel['criterios']['fecha_cbte_desde'].value 78 | cae = panel['criterios']['cae'].value 79 | aceptado = panel['criterios']['aceptado'].value 80 | 81 | # utilizar la funcion para obtener los comprobantes (desde SIRED/WSFEv1) 82 | kwargs = dict(tipo_cbte=tipo_cbte, punto_vta=pto_vta, 83 | cbte_desde=nro_cbte_desde, cbte_hasta=nro_cbte_hasta) 84 | for reg in fn(**kwargs): 85 | # convertir a fecha el formato de AFIP 86 | if not isinstance(reg['fecha_cbte'], datetime.date): 87 | reg['fecha_cbte'] = datetime.datetime.strptime(reg['fecha_cbte'], "%Y%m%d").date() 88 | # aplico filtros 89 | if aceptado and reg['resultado'] != 'A': 90 | continue 91 | if nro_doc and (reg['nro_doc'] != nro_doc or reg['tipo_doc'] != tipo_doc): 92 | print "filtrado por nro_doc", tipo_doc, nro_doc 93 | continue 94 | if nombre_cliente and not reg['nombre_cliente'].lower().startswith(nombre_cliente.lower()): 95 | print "filtrado por nombre" 96 | continue 97 | if tipo_cbte and reg['tipo_cbte'] != tipo_cbte: 98 | print "filtrado por tipo_cbte" 99 | continue 100 | if pto_vta and reg['punto_vta'] != pto_vta: 101 | print "filtrado por punto_vta", pto_vta, reg['punto_vta'] 102 | continue 103 | if nro_cbte_desde and not reg['cbte_nro'] >= nro_cbte_desde: 104 | print "filtrado por cbte_nro_desde", nro_cbte_desde, reg['cbte_nro'] 105 | continue 106 | if nro_cbte_hasta and not reg['cbte_nro'] <= nro_cbte_hasta: 107 | print "filtrado por cbte_nro_hasta", nro_cbte_hasta, reg['cbte_nro'] 108 | continue 109 | if fecha_cbte_desde and not reg['fecha_cbte'] >= fecha_cbte_desde: 110 | print "filtrado por fecha_cbte_desde", fecha_cbte_desde, reg['fecha_cbte'] 111 | continue 112 | if fecha_cbte_hasta and not reg['fecha_cbte'] <= fecha_cbte_hasta: 113 | print "filtrado por fecha_cbte_hasta", fecha_cbte_hasta, reg['fecha_cbte'] 114 | continue 115 | if cae and reg['cae'] != cae: 116 | print "filtrado por cae", cae, reg['cae'] 117 | continue 118 | # agrego el registro al listado: 119 | for it in reg.get('ivas', reg.get('iva', [])): 120 | reg['imp_iva_%d' % it['iva_id']] = it['importe'] 121 | listado.items[reg['id']] = reg 122 | 123 | 124 | def exportar(evt): 125 | from pyafipws.formatos.formato_csv import desaplanar, aplanar, escribir 126 | # adapto los datos al formato de pyrece: 127 | items = [item.copy() for item in listado.items] 128 | for item in items: 129 | item['cbt_numero'] = item['cbte_nro'] 130 | item['fecha_cbte'] = item['fecha_cbte'].strftime("%Y%m%d") 131 | if not 'tributos' in items: 132 | item['tributos'] = [] 133 | if not 'ivas' in items: 134 | item['ivas'] = [] 135 | filas1 = aplanar(items) 136 | print filas1 137 | # elejir nombre de archivo: 138 | result = gui.save_file(title='Guardar', filename="facturas-exportar.csv", 139 | wildcard='|'.join(["Archivos CSV (*.csv)|*.csv"])) 140 | if result: 141 | filename = result[0] 142 | escribir(filas1, filename) 143 | 144 | def reporte(evt): 145 | # elejir nombre de archivo: 146 | result = gui.save_file(title='Guardar', filename="facturas-reporte.html", 147 | wildcard='|'.join(["Archivos HTML (*.html)|*.html"])) 148 | if result: 149 | filename = result[0] 150 | f = open(filename, "w") 151 | f.write("\n") 152 | f.write("\n") 153 | for col in listado: 154 | f.write("\n" % col.text) 155 | f.write("\n") 156 | for it in listado.items: 157 | f.write("") 158 | for col in listado: 159 | if it[col.name] is None: 160 | v = "" 161 | elif callable(col.represent): 162 | v = col.represent(it[col.name]) 163 | elif col.represent: 164 | v = col.represent % it[col.name] 165 | else: 166 | v = it[col.name] 167 | if isinstance(v, unicode): 168 | v = v.encode("latin1", "ignore") 169 | f.write("" % v) 170 | f.write("\n") 171 | f.write("
%s
%s
\n") 172 | f.close() 173 | 174 | 175 | # --- gui2py designer generated code starts --- 176 | 177 | with gui.Window(name='consultas', 178 | title=u'Aplicativo Facturaci\xf3n Electr\xf3nica', 179 | resizable=True, height='636px', left='181', top='52', 180 | width='794px', image='', ): 181 | with gui.MenuBar(name='menubar_83_155', ): 182 | with gui.Menu(name='menu_114', ): 183 | gui.MenuItemSeparator(name='menuitemseparator_130', ) 184 | gui.StatusBar(name='statusbar_15_91', 185 | text=u'Servicio Web Factura Electr\xf3nica mercado interno (WSFEv1)', ) 186 | with gui.Panel(label=u'', name='panel', image='', ): 187 | gui.Image(name='image_507_571', height='36', left='17', top='545', 188 | width='238', filename='sistemas-agiles.png', ) 189 | gui.Image(name='image_33_540', height='50', left='665', top='532', 190 | width='100', filename='logo-pyafipws.png', ) 191 | with gui.ListView(name='listado', height='353', left='7', top='168', 192 | width='775', item_count=0, sort_column=-1, ): 193 | gui.ListColumn(name=u'tipo_cbte', text='Tipo Cbte') 194 | gui.ListColumn(name=u'punto_vta', text='Punto Venta', 195 | represent="%s", width=50, align="right") 196 | gui.ListColumn(name=u'cbte_nro', text='Nro Cbte', 197 | represent="%s", align="right") 198 | gui.ListColumn(name=u'fecha_cbte', text='Fecha Cbte', 199 | align="center") 200 | gui.ListColumn(name=u'tipo_doc', text='Tipo Doc', width=50) 201 | gui.ListColumn(name=u'nro_doc', text='Nro Doc', width=100, 202 | represent="%s", align="right") 203 | gui.ListColumn(name=u'nombre_cliente', text='Cliente', width=150) 204 | gui.ListColumn(name=u'id_impositivo', text='Cond. IVA', width=50, 205 | represent="%s", align="left") 206 | gui.ListColumn(name=u'imp_total', text='Total', width=100, 207 | represent=lambda x: locale.format("%.2f", x), align="right") 208 | gui.ListColumn(name=u'imp_op_ex', text='Exento', width=100, 209 | represent=lambda x: locale.format("%.2f", x), align="right") 210 | gui.ListColumn(name=u'imp_tot_conc', text='No Gravado', width=100, 211 | represent=lambda x: locale.format("%.2f", x), align="right") 212 | gui.ListColumn(name=u'imp_neto', text='Neto', width=100, 213 | represent=lambda x: locale.format("%.2f", x), align="right") 214 | gui.ListColumn(name=u'imp_iva_4', text='IVA 10.5%', width=100, 215 | represent=lambda x: locale.format("%.2f", x), align="right") 216 | gui.ListColumn(name=u'imp_iva_5', text='IVA 21%', width=100, 217 | represent=lambda x: locale.format("%.2f", x), align="right") 218 | gui.ListColumn(name=u'imp_iva_6', text='IVA 27%', width=100, 219 | represent=lambda x: locale.format("%.2f", x), align="right") 220 | gui.ListColumn(name=u'imp_trib', text='Tributos', width=100, 221 | represent=lambda x: locale.format("%.2f", x), align="right") 222 | gui.ListColumn(name=u'fecha_vto', text='Vto. CAE', align="left", 223 | represent=lambda x: str(x) if x else "") 224 | gui.ListColumn(name=u'cae', text='CAE', width=50, align="right", 225 | represent=lambda x: str(x) if x else "") 226 | gui.Button(label=u'Recuperar', name=u'recuperar', left='205', top='542', 227 | width='80', default=True) 228 | gui.Button(label=u'Buscar', name=u'buscar', left='295', top='542', 229 | width='75', default=True, onclick=buscar) 230 | gui.Label(name='label_22_147', left='12', top='144', 231 | text=u'Resultados:', ) 232 | with gui.Panel(label=u'Criterios de B\xfasqueda:', name='criterios', 233 | height='135', left='6', top='9', width='778', 234 | bgcolor=u'#F9F9F8', fgcolor=u'#4C4C4C', image='', ): 235 | gui.Label(name='label_182_163', height='21', left='16', top='31', 236 | width='38', text=u'Cliente:', ) 237 | gui.ComboBox(name='tipo_doc', text=u'CF', left='75', top='23', 238 | width='78', onchange=on_tipo_doc_change, 239 | items=[u'CUIT', u'DNI', u'CF', u'Pasaporte'], 240 | selection=3, value=u'CF', ) 241 | gui.TextBox(mask='##-########-#', name='nro_doc', left='164', 242 | top='24', width='110', text=u'20-26756539-3', 243 | value=u'20-26756539-3', ) 244 | gui.Label(name='label_268_164', height='31', left='295', top='28', 245 | width='61', text=u'Nombre:', ) 246 | gui.TextBox(name='nombre_cliente', left='367', top='23', 247 | width='240', value=u'Mariano Reingart', ) 248 | gui.Label(name='label_24_16', height='17', left='12', top='64', 249 | width='146', text=u'Tipo Comprobante:', ) 250 | gui.ComboBox(name=u'tipo_cbte', left='151', top='58', width='170', 251 | items=[u'Factura A', u'Factura B', u'Factura C', ], ) 252 | gui.Label(name='label_356_21_178', height='17', left='262', 253 | top='96', width='20', text=u'Hasta:', ) 254 | gui.TextBox(mask='####', name=u'pto_vta', alignment='right', 255 | left='365', top='59', width='47', value=99, ) 256 | gui.Label(name='label_356_21_155', height='17', left='15', 257 | top='96', width='60', text=u'Fecha:', ) 258 | gui.Label(id=2293, name='label_356_21_178_2293', height='17', 259 | left='329', top='64', width='29', text=u'P.V.:', ) 260 | gui.Label(id=2591, name='label_356_21_178_2591', height='17', 261 | left='72', top='96', width='47', text=u'Desde:', ) 262 | gui.TextBox(id=2794, mask='date', name=u'fecha_cbte_hasta', 263 | height='29', left='308', top='91', width='122', 264 | bgcolor=u'#F2F1F0', fgcolor=u'#4C4C4C', 265 | value=datetime.date(2014, 5, 27), ) 266 | gui.TextBox(id=290, mask='date', name=u'fecha_cbte_desde', 267 | height='29', left='131', top='91', width='122', 268 | bgcolor=u'#F2F1F0', fgcolor=u'#4C4C4C', 269 | value=datetime.date(2014, 5, 27), ) 270 | gui.TextBox(id=2361, mask='########', name=u'nro_cbte_hasta', 271 | alignment='right', height='27', left='654', top='59', 272 | width='92', bgcolor=u'#FFFFFF', fgcolor=u'#000000', 273 | value=12345678, ) 274 | gui.TextBox(mask='########', name=u'nro_cbte_desde', 275 | alignment='right', height='27', left='481', top='59', 276 | width='92', bgcolor=u'#FFFFFF', fgcolor=u'#000000', 277 | value=12345678, ) 278 | gui.Label(name='label_26_372_2499_2861', height='17', left='439', 279 | top='98', width='39', text=u'CAE:', ) 280 | gui.TextBox(name='cae', left='480', top='93', width='153', 281 | text=u'123456789012345', 282 | tooltip=u'CAE o c\xf3digo de barras', 283 | value=u'123456789012345', ) 284 | gui.CheckBox(label=u'Aprobado', name=u'aceptado', height='24', 285 | left='655', top='94', width='114', value=True, ) 286 | gui.Label(id=1243, name='label_356_21_178_2591_1243', height='17', 287 | left='423', top='63', width='47', text=u'Desde:', ) 288 | gui.Label(id=1343, name='label_356_21_178_1343', height='17', 289 | left='593', top='64', width='44', text=u'Hasta:', ) 290 | gui.Button(label=u'Cargar', name=u'cargar', left='379', top='542', 291 | width='73', fgcolor=u'#4C4C4C', ) 292 | gui.Button(label=u'Exportar', name=u'exportar', left='458', top='542', 293 | width='75', fgcolor=u'#4C4C4C', onclick=exportar) 294 | gui.Button(id=188, label=u'Reporte', name=u'reporte', left='542', 295 | top='542', width='75', fgcolor=u'#4C4C4C', onclick=reporte) 296 | 297 | # --- gui2py designer generated code ends --- 298 | 299 | # obtener referencia a la ventana principal: 300 | mywin = gui.get("consultas") 301 | panel = mywin['panel'] 302 | listado = panel['listado'] 303 | 304 | def main(callback=None, recuperar_fn=None): 305 | global mywin, panel, listado 306 | # limpiar valores del diseñador: 307 | panel['criterios']['tipo_doc'].items = datos.TIPO_DOC_MAP 308 | panel['criterios']['tipo_doc'].value = 80 # CUIT 309 | panel['criterios']['nro_doc'].value = None 310 | panel['criterios']['nombre_cliente'].value = "" 311 | panel['criterios']['tipo_cbte'].items = datos.TIPO_CBTE_MAP 312 | panel['criterios']['tipo_cbte'].value = None 313 | panel['criterios']['pto_vta'].value = None 314 | panel['criterios']['nro_cbte_desde'].value = None 315 | panel['criterios']['nro_cbte_hasta'].value = None 316 | # utilizar el mes actual (dia inicio y finalización): 317 | hoy = datetime.date.today() 318 | r = calendar.monthrange(hoy.year, hoy.month) 319 | desde = datetime.date(hoy.year, hoy.month, r[0] + 1) 320 | hasta = datetime.date(hoy.year, hoy.month, r[1]) 321 | panel['criterios']['fecha_cbte_hasta'].value = hasta 322 | panel['criterios']['fecha_cbte_desde'].value = desde 323 | panel['criterios']['cae'].value = "" 324 | 325 | # mostrar la representación más amigable de los códigos de AFIP 326 | listado['tipo_doc'].represent = lambda x: datos.TIPO_DOC_MAP[x] 327 | listado['tipo_cbte'].represent = lambda x: datos.TIPO_CBTE_MAP[x] 328 | listado['fecha_cbte'].represent = lambda x: x.strftime("%x") 329 | 330 | if callback: 331 | panel['cargar'].onclick = lambda evt: callback(listado.get_selected_items()[0]) 332 | panel['recuperar'].onclick = lambda evt: buscar(evt, recuperar_fn) 333 | 334 | mywin.show() 335 | 336 | if __name__ == "__main__": 337 | main() 338 | gui.main_loop() 339 | 340 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . -------------------------------------------------------------------------------- /factura.pyw: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | "Aplicativo Factura Electronica Libre" 5 | 6 | from __future__ import with_statement # for python 2.5 compatibility 7 | 8 | __author__ = "Mariano Reingart (reingart@gmail.com)" 9 | __copyright__ = "Copyright (C) 2014- Mariano Reingart" 10 | __license__ = "GPL 3.0+" 11 | __version__ = "0.10b" 12 | 13 | # Documentación: http://www.sistemasagiles.com.ar/trac/wiki/PyFactura 14 | 15 | import datetime # base imports, used by some controls and event handlers 16 | import decimal 17 | import os 18 | import pprint 19 | import time 20 | import traceback 21 | import sys 22 | from ConfigParser import SafeConfigParser 23 | 24 | import gui # import gui2py package (shortcuts) 25 | 26 | from pyafipws.padron import PadronAFIP 27 | from pyafipws.ws_sr_padron import WSSrPadronA4 28 | from pyafipws.sired import SIRED 29 | from pyafipws.wsaa import WSAA 30 | from pyafipws.wsfev1 import WSFEv1 31 | from pyafipws.pyfepdf import FEPDF 32 | from pyafipws.pyemail import PyEmail 33 | 34 | # set default locale to handle correctly numeric format (maskedit): 35 | import wx, locale 36 | if sys.platform == "win32": 37 | locale.setlocale(locale.LC_ALL, 'Spanish_Argentina.1252') 38 | elif sys.platform == "linux2": 39 | locale.setlocale(locale.LC_ALL, 'es_AR.utf8') 40 | loc = wx.Locale(wx.LANGUAGE_DEFAULT, wx.LOCALE_LOAD_DEFAULT) 41 | 42 | # configuración general 43 | 44 | CONFIG_FILE = "rece.ini" 45 | HOMO = WSAA.HOMO or WSFEv1.HOMO 46 | import datos 47 | 48 | # --- here go your event handlers --- 49 | 50 | def on_tipo_doc_change(evt): 51 | ctrl = evt.target 52 | value = "" 53 | if ctrl.value == 80: 54 | mask = '##-########-#' 55 | elif ctrl.value == 99: 56 | mask = '#' 57 | value = "0" 58 | on_nro_doc_change(evt) 59 | else: 60 | mask = '########' 61 | panel['cliente']['nro_doc'].mask = mask 62 | panel['cliente']['nro_doc'].value = value 63 | 64 | def on_nro_doc_change(evt): 65 | ctrl = panel['cliente']['nro_doc'] 66 | doc_nro = ctrl.value 67 | tipo_doc = panel['cliente']['tipo_doc'].value 68 | cat_iva = None 69 | doc_nro = doc_nro.replace("-", "") if doc_nro else 0 70 | if doc_nro: 71 | if padron.Buscar(doc_nro, tipo_doc): 72 | panel['cliente']['nombre'].value = padron.denominacion 73 | panel['cliente']['domicilio'].value = "" 74 | try: 75 | cat_iva = int(padron.cat_iva) 76 | except ValueError: 77 | pass 78 | padron.ConsultarDomicilios(doc_nro, tipo_doc) 79 | # tomar el primer domicilio: 80 | for domicilio in padron.domicilios: 81 | panel['cliente']['domicilio'].value = domicilio 82 | break 83 | # si está disponible el webservice, consultar con AFIP y actualizar: 84 | if padron_a4: 85 | try: 86 | padron_a4.Consultar(doc_nro) 87 | panel['cliente']['nombre'].value = padron_a4.denominacion 88 | if padron_a4.domicilios: 89 | panel['cliente']['domicilio'].value = padron_a4.domicilio 90 | if padron_a4.cat_iva: 91 | cat_iva = int(padron_a4.cat_iva) 92 | except Exception as ex: 93 | ## gui.alert(unicode(ex), "Excepción consultando Padron AFIP") 94 | # forzar selección de categoría por el usuario: 95 | cat_iva = 5 # consumidor final? 96 | panel['cliente']['email'].value = padron.email or "" 97 | else: 98 | panel['cliente']['nombre'].value = "" 99 | panel['cliente']['domicilio'].value = "" 100 | panel['cliente']['email'].value = "" 101 | panel['cliente']['cat_iva'].value = cat_iva 102 | 103 | def on_cat_iva_change(evt): 104 | ctrl = evt.target 105 | panel = ctrl.get_parent().get_parent() 106 | cat_iva = ctrl.value 107 | if cat_iva_emisor == 1: 108 | if cat_iva == 1: 109 | tipo_cbte = 1 # factura A 110 | else: 111 | tipo_cbte = 6 # factura B 112 | else: 113 | tipo_cbte = 11 114 | panel['tipo_cbte'].value = tipo_cbte 115 | 116 | def on_tipo_cbte_change(evt): 117 | panel = evt.target.get_parent() 118 | # solo cambiar si es no es de solo lectura (por ej cargando desde bd) 119 | if panel['nro_cbte'].editable: 120 | tipo_cbte = panel['tipo_cbte'].value 121 | pto_vta = panel['pto_vta'].value = pto_vta_emisor 122 | if tipo_cbte and pto_vta: 123 | nro_cbte = wsfev1.CompUltimoAutorizado(tipo_cbte, pto_vta) 124 | print wsfev1.Excepcion, wsfev1.ErrMsg 125 | else: 126 | nro_cbte = -1 127 | nro_cbte = int(nro_cbte) + 1 128 | panel['nro_cbte'].value = nro_cbte 129 | 130 | def on_servicios_change(evt): 131 | # habilitar fechas de servicio solo si corresponde: 132 | panel['periodo'].enabled = evt.target.value 133 | 134 | 135 | def limpiar(evt, confirmar=False): 136 | if confirmar: 137 | if not gui.confirm(u"¿Se perderán todos los campos?", "Limpiar"): 138 | return 139 | today = datetime.datetime.today() 140 | desde = today - datetime.timedelta(today.day - 1) 141 | hasta = desde + datetime.timedelta(today.day + 31) 142 | hasta = hasta - datetime.timedelta(hasta.day) 143 | panel['cliente']['nro_doc'].value = "" 144 | panel['cliente']['nombre'].value = "" 145 | panel['cliente']['domicilio'].value = "" 146 | panel['cliente']['email'].value = "" 147 | panel['cliente']['cat_iva'].value = None 148 | panel['tipo_cbte'].value = None 149 | panel['fecha_cbte'].value = today 150 | panel['nro_cbte'].value = 0 151 | panel['periodo']['fecha_venc_pago'].value = today 152 | panel['periodo']['fecha_desde'].value = desde 153 | panel['periodo']['fecha_hasta'].value = hasta 154 | panel['aut']['cae'].value = "" 155 | panel['aut']['fecha_vto_cae'].value = None 156 | panel['notebook']['obs']['generales'].value = "" 157 | panel['notebook']['obs']['comerciales'].value = "" 158 | panel['notebook']['obs']['afip'].value = "" 159 | panel['notebook']['tributos']['grilla'].items.clear() 160 | grilla.items.clear() 161 | recalcular() 162 | habilitar(True) 163 | 164 | def habilitar(valor=True): 165 | panel['tipo_cbte'].enabled = valor 166 | panel['pto_vta'].enabled = valor 167 | panel['nro_cbte'].enabled = valor 168 | panel['fecha_cbte'].enabled = valor 169 | panel['cliente'].enabled = valor 170 | panel['conceptos'].enabled = valor 171 | panel['periodo'].enabled = valor 172 | panel['grabar'].enabled = valor 173 | panel['notebook']['tab_art'].enabled = valor 174 | panel['notebook']['alicuotas_iva'].enabled = valor 175 | panel['notebook']['tributos'].enabled = valor 176 | panel['notebook']['obs'].enabled = valor 177 | panel['aut']['obtener'].enabled = not valor 178 | panel['aut']['imprimir'].enabled = False 179 | panel['aut']['enviar'].enabled = False 180 | panel['nro_cbte'].editable = valor 181 | 182 | def on_grid_cell_change(evt): 183 | grid = evt.target 184 | value = evt.detail 185 | col_name = grid.columns[evt.col].name 186 | if col_name == "codigo": 187 | grid.items[evt.row]['ds'] = datos.articulos.get(value, "") 188 | recalcular() 189 | 190 | def on_agregar_click(evt): 191 | grilla.items.append({'qty': 1, 'precio': 0., 'iva_id': 5}) 192 | 193 | def on_borrar_click(evt): 194 | if grilla.items: 195 | del grilla.items[-1] 196 | 197 | def on_agregar_tributo_click(evt): 198 | base_imp = recalcular() 199 | listado = panel['notebook']['tributos']['grilla'] 200 | listado.items.append({'tributo_id': 99, 'desc': '', 201 | 'alicuota': 0., 'base_imp': base_imp, }) 202 | 203 | def on_borrar_tributo_click(evt): 204 | listado = panel['notebook']['tributos']['grilla'] 205 | if listado .items: 206 | del listado .items[-1] 207 | recalcular() 208 | 209 | 210 | def recuperar(tipo_cbte=None, punto_vta=None, cbte_desde=None, cbte_hasta=None): 211 | "Consultar y rearmar comprobantes registrados en el Webservice de AFIP" 212 | title = __doc__ 213 | facturas = [] 214 | if not tipo_cbte: 215 | gui.alert("Debe completar el tipo de comprobante", title) 216 | ##tipo_cbte = 1 217 | if not punto_vta: 218 | gui.alert("Debe completar el punto de venta", title) 219 | ##punto_vta = 1 220 | if not cbte_desde: 221 | gui.alert("Debe completar el número de comprobante inicial", title) 222 | ##cbte_desde = 1 223 | if not cbte_hasta: 224 | gui.alert("Debe completar el número de comprobante final", title) 225 | ##cbte_hasta = cbte_desde + 1 226 | if all([tipo_cbte, punto_vta, cbte_desde, cbte_hasta]): 227 | for cbte_nro in range(cbte_desde, cbte_hasta + 1): 228 | print "Recuperando", tipo_cbte, punto_vta, cbte_nro, 229 | ok = wsfev1.CompConsultar(tipo_cbte, punto_vta, cbte_nro) 230 | print wsfev1.Resultado, wsfev1.CAE, wsfev1.Excepcion, wsfev1.ErrMsg 231 | if not ok: 232 | continue 233 | factura = wsfev1.factura.copy() 234 | if ok and factura: 235 | pprint.pprint(factura) 236 | factura["id"] = len(facturas) + 1 237 | factura["cbte_nro"] = factura["cbt_desde"] 238 | factura["nombre_cliente"] = "" 239 | factura["id_impositivo"] = "" 240 | factura["email"] = "" 241 | factura["cat_iva"] = "" 242 | factura["domicilio_cliente"] = "" 243 | factura["obs_generales"] = "" 244 | factura["obs_comerciales"] = "" 245 | factura["forma_pago"] = "" 246 | factura["motivo_obs"] = wsfev1.Obs 247 | factura["fecha_vto"] = datetime.datetime.strptime( 248 | wsfev1.Vencimiento, "%Y%m%d").date() 249 | # obtener datos del cliente desde el padrón 250 | if padron.Buscar(factura["nro_doc"], factura["tipo_doc"]): 251 | factura["nombre_cliente"] = padron.denominacion 252 | try: 253 | factura["cat_iva"] = int(padron.cat_iva) 254 | except ValueError: 255 | pass 256 | if padron_a4: 257 | try: 258 | padron_a4.Consultar(factura["nro_doc"]) 259 | factura["nombre_cliente"] = padron_a4.denominacion 260 | factura["domicilio_cliente"] = padron_a4.domicilio 261 | except: 262 | pass 263 | factura["email"] = padron.email or "" 264 | 265 | # rearmar campos no informados en este Webservice: 266 | factura["detalles"] = [] 267 | for i, iva in enumerate(factura["iva"]): 268 | it = {'ds': "Art. %s" % (i + 1), "umed": 0, 'codigo': "", 269 | 'qty': 1, 'precio': iva["base_imp"], 'bonif': 0, 270 | 'iva_id': iva["iva_id"], 'imp_iva': iva["importe"], 271 | 'subtotal': iva["base_imp"] + iva["importe"]} 272 | factura["detalles"].append(it) 273 | facturas.append(factura) 274 | return facturas 275 | 276 | def on_consultas(evt): 277 | import consultas 278 | # Workaround: recreate gui objects if the window was closed (TODO: Fix) 279 | try: 280 | gui.get("consultas").title 281 | except: 282 | reload(consultas) 283 | consultas.main(callback=cargar_factura, recuperar_fn=recuperar) 284 | 285 | def recalcular(evt=None): 286 | tipo_cbte = panel['tipo_cbte'].value 287 | neto_iva = {} 288 | imp_iva = {} 289 | tasas_iva = {1: None, 2: None, 3: 0, 4: 10.5, 5: 21, 6: 27, 8: 5, 9: 2.5} 290 | total = 0. 291 | for it in grilla.items: 292 | iva_id = it['iva_id'] 293 | qty = it['qty'] 294 | precio = it['precio'] 295 | subtotal = qty * precio 296 | it['subtotal'] = subtotal 297 | total += subtotal 298 | it['imp_iva'] = None 299 | if iva_id in tasas_iva: 300 | if not iva_incluido or tasas_iva[iva_id] is None: 301 | neto_item = subtotal 302 | else: 303 | neto_item = round(subtotal / (100. + tasas_iva[iva_id]) * 100., 2) 304 | neto_iva[iva_id] = neto_iva.get(iva_id, 0.) + neto_item 305 | if tasas_iva[iva_id] is not None and not tipo_cbte in datos.CLASE_C: 306 | iva_liq = neto_item * tasas_iva[iva_id] / 100. 307 | imp_iva[iva_id] = imp_iva.get(iva_id, 0.) + iva_liq 308 | it['imp_iva'] = iva_liq 309 | imp_trib = 0.00 310 | listado = panel['notebook']['tributos']['grilla'] 311 | for it in listado.items: 312 | base_imp = it['base_imp'] or 0.00 313 | alic = it['alic'] or 0.00 314 | importe = it["importe"] = round(base_imp * alic / 100., 2) 315 | imp_trib += importe 316 | listado = panel['notebook']['alicuotas_iva']['listado'] 317 | listado.items.clear() 318 | for iva_id, iva_liq in imp_iva.items(): 319 | listado.items[str(iva_id)] = {'iva_id': iva_id, 'importe': iva_liq, 320 | 'base_imp': neto_iva[iva_id], 321 | 'alicuota': tasas_iva[iva_id]} 322 | # excluir exento y no gravado del calculo del neto: 323 | neto = sum([nt for iva_id, nt in neto_iva.items() 324 | if tasas_iva.get(iva_id) is not None]) 325 | panel['notebook']['alicuotas_iva']['imp_neto'].value = neto 326 | panel['notebook']['alicuotas_iva']['imp_tot_conc'].value = neto_iva.get(1, 0) 327 | panel['notebook']['alicuotas_iva']['imp_op_ex'].value = neto_iva.get(2, 0) 328 | panel['imp_iva'].value = sum(imp_iva.values(), 0.) 329 | panel['imp_trib'].value = imp_trib 330 | if not iva_incluido: 331 | total += sum(imp_iva.values(), 0.) 332 | total += imp_trib 333 | panel['imp_total'].value = total 334 | # calcular subtotal general (neto sin IVA, incluyendo exento y no gravado): 335 | subtotal_neto = sum([nt for iva_id, nt in neto_iva.items()]) 336 | return subtotal_neto 337 | 338 | def obtener_cae(evt): 339 | global id_factura 340 | if not id_factura: 341 | grabar(evt) 342 | tipo_cbte = panel['tipo_cbte'].value 343 | punto_vta = panel['pto_vta'].value 344 | cbte_nro = panel['nro_cbte'].value 345 | fecha_cbte = panel['fecha_cbte'].value.strftime("%Y%m%d") 346 | concepto = 0 347 | if panel['conceptos']['productos'].value: 348 | concepto += 1 349 | if panel['conceptos']['servicios'].value: 350 | concepto += 2 351 | tipo_doc = panel['cliente']['tipo_doc'].value 352 | nro_doc = panel['cliente']['nro_doc'].value.replace("-", "") 353 | # Redondear valores a 2 decimales para superar validación 10056 de AFIP 354 | imp_neto = "%0.2f" % panel['notebook']['alicuotas_iva']['imp_neto'].value 355 | imp_iva = "%0.2f" % panel['imp_iva'].value 356 | imp_trib = "%0.2f" % panel['imp_trib'].value 357 | imp_op_ex = "%0.2f" % panel['notebook']['alicuotas_iva']['imp_op_ex'].value 358 | imp_tot_conc = "%0.2f" % panel['notebook']['alicuotas_iva']['imp_tot_conc'].value 359 | imp_total = "%0.2f" % panel['imp_total'].value 360 | # solo informar si es servicio (no informar para productos) 361 | if concepto > 1: 362 | fecha_venc_pago = panel['periodo']['fecha_venc_pago'].value.strftime("%Y%m%d") 363 | fecha_serv_desde = panel['periodo']['fecha_desde'].value.strftime("%Y%m%d") 364 | fecha_serv_hasta = panel['periodo']['fecha_hasta'].value.strftime("%Y%m%d") 365 | else: 366 | fecha_venc_pago = fecha_serv_desde = fecha_serv_hasta = None 367 | moneda_id = 'PES'; moneda_ctz = '1.000' 368 | wsfev1.CrearFactura(concepto, tipo_doc, nro_doc, tipo_cbte, punto_vta, 369 | cbte_nro, cbte_nro, imp_total, imp_tot_conc, imp_neto, 370 | imp_iva, imp_trib, imp_op_ex, fecha_cbte, fecha_venc_pago, 371 | fecha_serv_desde, fecha_serv_hasta, #-- 372 | moneda_id, moneda_ctz) 373 | 374 | if False: 375 | tipo = 19 376 | pto_vta = 2 377 | nro = 1234 378 | wsfev1.AgregarCmpAsoc(tipo, pto_vta, nro) 379 | 380 | listado = panel['notebook']['tributos']['grilla'] 381 | for it in listado.items: 382 | tributo_id = it['tributo_id'] 383 | desc = it['desc'] or "" 384 | base_imp = it['base_imp'] 385 | alic = it['alic'] 386 | importe = it['importe'] 387 | wsfev1.AgregarTributo(tributo_id, desc, base_imp, alic, importe) 388 | 389 | listado = panel['notebook']['alicuotas_iva']['listado'] 390 | for it in listado.items: 391 | iva_id = it['iva_id'] 392 | base_imp = "%0.2f" % it['base_imp'] 393 | importe = "%0.2f" % it['importe'] 394 | wsfev1.AgregarIva(iva_id, base_imp, importe) 395 | 396 | try: 397 | wsfev1.CAESolicitar() 398 | panel['aut']['cae'].value = wsfev1.CAE 399 | vto = datetime.datetime.strptime(wsfev1.Vencimiento, "%Y%m%d").date() 400 | panel['aut']['fecha_vto_cae'].value = vto 401 | panel['notebook']['obs']['afip'].value = wsfev1.Obs 402 | except: 403 | print wsfev1.Excepcion, wsfev1.ErrMsg 404 | print wsfev1.XmlRequest, wsfev1.XmlResponse 405 | panel['aut']['aceptado'].value = wsfev1.Resultado == "A" 406 | panel['aut']['rechazado'].value = wsfev1.Resultado == "R" 407 | if wsfev1.Excepcion: 408 | gui.alert(wsfev1.Excepcion, u"Excepcion") 409 | if wsfev1.Obs: 410 | gui.alert(wsfev1.Obs, u"Observaciones AFIP") 411 | if wsfev1.ErrMsg: 412 | gui.alert(wsfev1.ErrMsg, u"Mensajes Error AFIP") 413 | 414 | # actualizar registro 415 | sired.EstablecerParametro("cae", wsfev1.CAE) 416 | sired.EstablecerParametro("fecha_vto", wsfev1.Vencimiento) 417 | sired.EstablecerParametro("motivo_obs", wsfev1.Obs) 418 | sired.EstablecerParametro("resultado", wsfev1.Resultado) 419 | sired.EstablecerParametro("reproceso", wsfev1.Reproceso) 420 | sired.EstablecerParametro("err_code", wsfev1.ErrCode) 421 | sired.EstablecerParametro("err_msg", wsfev1.ErrMsg) 422 | sired.ActualizarFactura(id_factura) 423 | 424 | if wsfev1.Resultado == "A": 425 | panel['aut']['imprimir'].enabled = True 426 | panel['aut']['enviar'].enabled = True 427 | 428 | def crear_factura(comp, imprimir=True): 429 | tipo_cbte = panel['tipo_cbte'].value or 6 430 | punto_vta = panel['pto_vta'].value 431 | cbte_nro = panel['nro_cbte'].value 432 | fecha_cbte = panel['fecha_cbte'].value.strftime("%Y%m%d") 433 | concepto = 0 434 | if panel['conceptos']['productos'].value: 435 | concepto += 1 436 | if panel['conceptos']['servicios'].value: 437 | concepto += 2 438 | tipo_doc = panel['cliente']['tipo_doc'].value 439 | nro_doc = panel['cliente']['nro_doc'].value.replace("-", "") 440 | imp_neto = panel['notebook']['alicuotas_iva']['imp_neto'].value 441 | imp_iva = panel['imp_iva'].value 442 | imp_trib = panel['imp_trib'].value 443 | imp_op_ex = panel['notebook']['alicuotas_iva']['imp_op_ex'].value 444 | imp_tot_conc = panel['notebook']['alicuotas_iva']['imp_tot_conc'].value 445 | imp_total = panel['imp_total'].value 446 | fecha = panel['periodo']['fecha_venc_pago'].value 447 | fecha_venc_pago = fecha.strftime("%Y%m%d") if fecha else None 448 | fecha = panel['periodo']['fecha_desde'].value 449 | fecha_serv_desde = fecha.strftime("%Y%m%d") if fecha else None 450 | fecha = panel['periodo']['fecha_hasta'].value 451 | fecha_serv_hasta = fecha.strftime("%Y%m%d") if fecha else None 452 | moneda_id = 'PES'; moneda_ctz = '1.000' 453 | obs_generales = panel['notebook']['obs']['generales'].value 454 | obs_comerciales = panel['notebook']['obs']['comerciales'].value 455 | nombre_cliente = panel['cliente']['nombre'].value 456 | email = panel['cliente']['email'].value 457 | cat_iva = panel['cliente']['cat_iva'].value or None 458 | # dividir el domicilio en lineas y ubicar los campos (solo al imprimir) 459 | if imprimir: 460 | domicilio = panel['cliente']['domicilio'].value.split("\n") 461 | domicilio_cliente = domicilio and domicilio[0] or "" 462 | else: 463 | domicilio = domicilio_cliente = panel['cliente']['domicilio'].value 464 | pais_dst_cmp = 200 # Argentina 465 | id_impositivo = panel['cliente']['cat_iva'].text 466 | forma_pago = panel['conceptos']['forma_pago'].text 467 | incoterms = 'FOB' 468 | idioma_cbte = 1 # español 469 | motivo = panel['notebook']['obs']['afip'].value 470 | cae = panel['aut']['cae'].value or 0 471 | vto = panel['aut']['fecha_vto_cae'].value 472 | fch_venc_cae = vto and vto.strftime("%Y%m%d") or "" 473 | 474 | comp.CrearFactura(concepto, tipo_doc, nro_doc, tipo_cbte, punto_vta, 475 | cbte_nro, imp_total, imp_tot_conc, imp_neto, 476 | imp_iva, imp_trib, imp_op_ex, fecha_cbte, fecha_venc_pago, 477 | fecha_serv_desde, fecha_serv_hasta, 478 | moneda_id, moneda_ctz, cae, fch_venc_cae, id_impositivo, 479 | nombre_cliente, domicilio_cliente, pais_dst_cmp, 480 | obs_comerciales, obs_generales, forma_pago, incoterms, 481 | idioma_cbte, motivo) 482 | 483 | comp.EstablecerParametro("email", email) 484 | comp.EstablecerParametro("cat_iva", cat_iva) 485 | 486 | if panel['aut']['aceptado'].value: 487 | resultado = "A" 488 | elif panel['aut']['rechazado'].value: 489 | resultado = "R" 490 | comp.EstablecerParametro("resultado", resultado) 491 | 492 | if False: 493 | tipo = 91 494 | pto_vta = 2 495 | nro = 1234 496 | comp.AgregarCmpAsoc(tipo, pto_vta, nro) 497 | 498 | listado = panel['notebook']['tributos']['grilla'] 499 | for it in listado.items: 500 | tributo_id = it['tributo_id'] 501 | desc = it['desc'] or "" 502 | base_imp = it['base_imp'] 503 | alic = it['alic'] 504 | importe = it['importe'] 505 | comp.AgregarTributo(tributo_id, desc, base_imp, alic, importe) 506 | 507 | listado = panel['notebook']['alicuotas_iva']['listado'] 508 | for it in listado.items: 509 | iva_id = it['iva_id'] 510 | base_imp = it['base_imp'] 511 | importe = it['importe'] 512 | comp.AgregarIva(iva_id, base_imp, importe) 513 | 514 | for it in grilla.items: 515 | u_mtx = "" 516 | cod_mtx = "" 517 | codigo = it['codigo'] or "" 518 | ds = it['ds'] or "" 519 | qty = it['qty'] 520 | umed = 7 521 | precio = it['precio'] 522 | bonif = 0.00 523 | iva_id = it['iva_id'] 524 | imp_iva = it['imp_iva'] or 0 525 | subtotal = it['subtotal'] or 0 526 | # no discriminar IVA si no es Factura clase A / M: 527 | if tipo_cbte not in (1, 2, 3, 4, 51, 52, 53, 54) and imprimir: 528 | if not iva_incluido: 529 | precio += imp_iva / qty 530 | # siempre mostrar subtotal c/IVA (idem AFIP): 531 | if imp_iva is not None and not iva_incluido and imprimir: 532 | subtotal += imp_iva 533 | despacho = "" 534 | comp.AgregarDetalleItem(u_mtx, cod_mtx, codigo, ds, qty, umed, 535 | precio, bonif, iva_id, imp_iva, subtotal, despacho) 536 | 537 | # datos fijos: 538 | for k, v in conf_pdf.items(): 539 | fepdf.AgregarDato(k, v) 540 | if k.upper() == 'CUIT': 541 | fepdf.CUIT = v # CUIT del emisor para código de barras 542 | 543 | if len(domicilio) > 1: 544 | comp.AgregarDato("Cliente.Localidad", domicilio[1]) 545 | if len(domicilio) > 2: 546 | comp.AgregarDato("Cliente.Provincia", domicilio[2]) 547 | 548 | def generar_pdf(evt, mostrar=True): 549 | crear_factura(fepdf) 550 | 551 | fepdf.CrearPlantilla(papel=conf_fact.get("papel", "legal"), 552 | orientacion=conf_fact.get("orientacion", "portrait")) 553 | if "borrador" in conf_pdf or HOMO: 554 | fepdf.AgregarDato("draft", conf_pdf.get("borrador", "HOMOLOGACION")) 555 | 556 | if "logo" in conf_pdf and not os.path.exists(conf_pdf["logo"]): 557 | fepdf.AgregarDato("logo", "") 558 | 559 | fepdf.ProcesarPlantilla(num_copias=int(conf_fact.get("copias", 1)), 560 | lineas_max=int(conf_fact.get("lineas_max", 24)), 561 | qty_pos=conf_fact.get("cant_pos") or 'izq') 562 | salida = conf_fact.get("salida", "") 563 | fact = fepdf.factura 564 | if salida: 565 | pass 566 | elif 'pdf' in fact and fact['pdf']: 567 | salida = fact['pdf'] 568 | else: 569 | # genero el nombre de archivo según datos de factura 570 | d = conf_fact.get('directorio', ".") 571 | clave_subdir = conf_fact.get('subdirectorio','fecha_cbte') 572 | if clave_subdir: 573 | d = os.path.join(d, fact[clave_subdir]) 574 | if not os.path.isdir(d): 575 | os.makedirs(d) 576 | fs = conf_fact.get('archivo','numero').split(",") 577 | it = fact.copy() 578 | tipo_fact, letra_fact, numero_fact = fact['_fmt_fact'] 579 | it['tipo'] = tipo_fact.replace(" ", "_") 580 | it['letra'] = letra_fact 581 | it['numero'] = numero_fact 582 | it['mes'] = fact['fecha_cbte'][4:6] 583 | it['año'] = fact['fecha_cbte'][0:4] 584 | fn = u''.join([unicode(it.get(ff,ff)) for ff in fs]) 585 | fn = fn.encode('ascii', 'replace').replace('?','_') 586 | salida = os.path.join(d, "%s.pdf" % fn) 587 | fepdf.GenerarPDF(archivo=salida) 588 | if mostrar: 589 | fepdf.MostrarPDF(archivo=salida, imprimir='--imprimir' in sys.argv) 590 | return salida 591 | 592 | def grabar(evt): 593 | global id_factura 594 | tipo_doc = panel['cliente']['tipo_doc'].value 595 | nro_doc = panel['cliente']['nro_doc'].value.replace("-", "") 596 | denominacion = panel['cliente']['nombre'].value 597 | direccion = panel['cliente']['domicilio'].value 598 | cat_iva = panel['cliente']['cat_iva'].value or None 599 | email = panel['cliente']['email'].value 600 | if not all([tipo_doc, nro_doc, denominacion]): 601 | gui.alert(u"Información del cliente incompleta", "Imposible Guardar") 602 | elif not grilla.items: 603 | gui.alert(u"No ingresó artículos", "Imposible Guardar") 604 | elif not panel['conceptos']['productos'].value and not panel['conceptos']['servicios'].value: 605 | gui.alert(u"Debe seleccionar productos, servicios o ambos", "Imposible Guardar") 606 | elif panel['imp_total'].value == 0 and not gui.confirm(u"¿Importe 0?", "Confirmar Guardar"): 607 | pass 608 | else: 609 | padron.Guardar(tipo_doc, nro_doc, denominacion, cat_iva, direccion, email) 610 | crear_factura(sired, imprimir=False) 611 | id_factura = sired.GuardarFactura() 612 | habilitar(False) 613 | 614 | def cargar(evt): 615 | if not gui.confirm(u"¿Se restableceran todos los campos?", u"Cargar última factura"): 616 | return 617 | sired.ObtenerFactura() 618 | f = sired.factura 619 | cargar_factura(f) 620 | 621 | def cdate(s): 622 | if isinstance(s, (datetime.datetime, datetime.date)): 623 | return s 624 | elif s: 625 | return datetime.datetime.strptime(s, "%Y%m%d").date() 626 | else: 627 | return None 628 | 629 | def cargar_factura(f): 630 | # cargar datos de cliente (dispara eventos) 631 | nro_doc = str(f["nro_doc"]) 632 | if f["tipo_doc"] == 80 and len(nro_doc) == 11: 633 | nro_doc = "%2s-%6s-%1s" % (nro_doc[0:2], nro_doc[2:10], nro_doc[10:]) 634 | panel['cliente']['tipo_doc'].value = f["tipo_doc"] 635 | panel['cliente']['nro_doc'].value = nro_doc 636 | panel['cliente']['nombre'].value = f["nombre_cliente"] 637 | panel['cliente']['email'].value = f["email"] 638 | panel['cliente']['cat_iva'].value = f["cat_iva"] or None 639 | panel['cliente']['domicilio'].value = f["domicilio_cliente"] 640 | # cargar datos del encabezado del comprobante 641 | panel['tipo_cbte'].value = f["tipo_cbte"] 642 | panel['pto_vta'].value = f["punto_vta"] 643 | panel['nro_cbte'].value = f["cbte_nro"] 644 | panel['nro_cbte'].editable = False # no permit que el evento lo cambie 645 | panel['fecha_cbte'].value = cdate(f["fecha_cbte"]) 646 | panel['conceptos']['productos'].value = f["concepto"] & 1 647 | panel['conceptos']['servicios'].value = f["concepto"] & 2 648 | panel['notebook']['alicuotas_iva']['imp_neto'].value = float(f["imp_neto"]) 649 | panel['imp_iva'].value = float(f["imp_iva"]) 650 | panel['imp_trib'].value = float(f["imp_trib"]) 651 | panel['notebook']['alicuotas_iva']['imp_op_ex'].value = float(f["imp_op_ex"]) 652 | panel['notebook']['alicuotas_iva']['imp_tot_conc'].value = float(f["imp_tot_conc"]) 653 | panel['imp_total'].value = float(f["imp_total"]) 654 | panel['periodo']['fecha_venc_pago'].value = f["fecha_venc_pago"] 655 | panel['periodo']['fecha_desde'].value = cdate(f["fecha_serv_desde"]) 656 | panel['periodo']['fecha_hasta'].value = cdate(f["fecha_serv_hasta"]) 657 | panel['notebook']['obs']['generales'].value = f["obs_generales"] 658 | panel['notebook']['obs']['comerciales'].value = f["obs_comerciales"] 659 | panel['conceptos']['forma_pago'].text = f["forma_pago"] 660 | panel['notebook']['obs']['afip'].value = f.get("motivo_obs") or "" 661 | panel['aut']['cae'].value = f.get("cae") or "" 662 | panel['aut']['fecha_vto_cae'].value = cdate(f.get("fecha_vto")) 663 | 664 | listado = panel['notebook']['alicuotas_iva']['listado'] 665 | listado.items.clear() 666 | # TODO: ajustar gui2py para soportar decimal... 667 | #for it in f.get("ivas", []): 668 | # listado.items[str(it['iva_id'])] = {'iva_id': it['iva_id'], 669 | # 'importe': it['importe'], 670 | # 'base_imp': it["base_imp"], 671 | # 'alicuota': it.get("alicuota")} 672 | grilla.items.clear() 673 | for it in f.get("detalles", []): 674 | for k, v in it.items(): 675 | if isinstance(v, decimal.Decimal): 676 | it[k] = round(float(v), 2) 677 | grilla.items.append(it) 678 | 679 | listado = panel['notebook']['tributos']['grilla'] 680 | listado.items.clear() 681 | for it in f.get("tributos", []): 682 | for k, v in it.items(): 683 | if isinstance(v, decimal.Decimal): 684 | it[k] = round(float(v), 2) 685 | listado.items.append(it) 686 | 687 | recalcular() 688 | habilitar(False) 689 | # habilitar reimpresión y envio si tiene CAE 690 | if f.get("cae") and str(f["cae"]).isdigit() and len(str(f["cae"])) == 14: 691 | panel['aut']['obtener'].enabled = False 692 | panel['aut']['imprimir'].enabled = True 693 | panel['aut']['enviar'].enabled = True 694 | 695 | def enviar(evt): 696 | tipo_cbte = panel['tipo_cbte'].text 697 | punto_vta = panel['pto_vta'].value 698 | cbte_nro = panel['nro_cbte'].value 699 | cbte = "%s %04d-%08d" % (tipo_cbte, punto_vta, cbte_nro) 700 | motivo = conf_mail['motivo'].replace("NUMERO", cbte) 701 | destinatario = panel['cliente']['email'].value 702 | mensaje = conf_mail['cuerpo'] 703 | archivo = generar_pdf(evt, mostrar=False) 704 | 705 | print "Motivo: ", motivo 706 | print "Destinatario: ", destinatario 707 | print "Mensaje: ", mensaje 708 | print "Archivo: ", archivo 709 | 710 | pyemail = PyEmail() 711 | pyemail.Conectar(conf_mail['servidor'], 712 | conf_mail['usuario'], conf_mail['clave'], 713 | conf_mail['puerto'],) 714 | if 'cc' in conf_mail: 715 | pyemail.AgregarCC(conf_mail['cc']) 716 | if 'bcc' in conf_mail: 717 | pyemail.AgregarBCC(conf_mail['bcc']) 718 | if pyemail.Enviar(conf_mail['remitente'], 719 | motivo, destinatario, mensaje, archivo): 720 | gui.alert("Correo \"%s\" enviado correctamente a %s" % 721 | (motivo, destinatario), "Enviar email", icon="info") 722 | else: 723 | print pyemail.Traceback 724 | gui.alert(pyemail.Excepcion, "Error al enviar email", icon="error") 725 | 726 | # --- gui2py designer generated code starts --- 727 | 728 | with gui.Window(name='mywin', visible=False, 729 | title=u'Aplicativo Facturaci\xf3n Electr\xf3nica', 730 | resizable=True, height='620px', left='181', top='52', 731 | width='653px', 732 | image=''): 733 | with gui.MenuBar(name='menubar_83_155', ): 734 | with gui.Menu(name='menu_114', ): 735 | gui.MenuItem(name='consultas', label='Consultas', 736 | onclick=on_consultas) 737 | gui.MenuItemSeparator(name='menuitemseparator_130', ) 738 | gui.StatusBar(name='statusbar_15_91', 739 | text=u'Servicio Web Factura Electr\xf3nica mercado interno (WSFEv1)', ) 740 | with gui.Panel(name='panel'): 741 | with gui.Panel(label=u'Cliente:', name='cliente', 742 | height='114', left='8', top='6', width='633', image='', ): 743 | gui.Label(name='label_182_163', height='25', left='11', 744 | top='24', width='38', text=u'Documento:', ) 745 | gui.ComboBox(name='tipo_doc', text=u'CF', 746 | left='111', top='20', width='78', 747 | value=80, onchange=on_tipo_doc_change, 748 | items=datos.TIPO_DOC_MAP, ) 749 | gui.TextBox(mask='##-########-#', name='nro_doc', 750 | left='192', top='20', width='110', 751 | value=u'20-26756539-3', onblur=on_nro_doc_change, 752 | ) 753 | gui.Label(name='label_268_164', height='31', left='316', 754 | top='24', width='61', text=u'Nombre:', ) 755 | gui.TextBox(name='nombre', 756 | left='383', top='20', width='240', 757 | value=u'Mariano Reingart', ) 758 | gui.Label(name='label_322_165', left='10', top='52', 759 | width='72', text=u'Domicilio:', ) 760 | gui.TextBox(name='domicilio', multiline=True, 761 | height='55', left='112', top='52', width='189', 762 | value=u'Castagna 4942', ) 763 | gui.Label(name='label_530_167', left='321', top='85', 764 | width='58', text=u'E-Mail:', ) 765 | gui.Label(name='label_530_167_1258', height='17', left='321', 766 | top='56', width='58', text=u'IVA:', ) 767 | gui.ComboBox(name='cat_iva', text=u'Responsable Inscripto', 768 | left='383', top='51', width='190', editable=False, 769 | onchange=on_cat_iva_change, 770 | items={1: u"Responsable Inscripto", 4: u"Exento", 771 | 5: u"Consumidor Final", 6: u"Monotributo", 772 | 8: u"Proveedor del Exterior", 773 | 9: u"Cliente del Exterior", 774 | 10: u"IVA Liberado - Ley Nº 19.640", 775 | 12: u"Pequeño Contribuyente Eventual", 776 | 13: u"Monotributista Social", 777 | 14: u"Pequeño Contribuyente Eventual Social", 778 | 15: u"IVA No Alcanzado"}, 779 | ) 780 | gui.TextBox(name='email', left='383', top='82', 781 | width='240', value=u'reingart@gmail.com', ) 782 | gui.Label(name='label_24_16', height='17', left='13', top='130', 783 | width='80', text=u'Comprobante:', ) 784 | gui.ComboBox(name=u'tipo_cbte', left='115', top='125', 785 | width='170', onchange=on_tipo_cbte_change, 786 | items=datos.TIPO_CBTE_MAP, 787 | text=u'', editable=False) 788 | gui.Label(name='label_356_21_178', height='17', left='290', 789 | top='130', width='20', text=u'N\xb0:', ) 790 | gui.TextBox(mask='####', name=u'pto_vta', alignment='right', 791 | left='318', top='125', width='47', 792 | value=99, ) 793 | gui.TextBox(mask='########', name=u'nro_cbte', alignment='right', 794 | left='366', top='125', width='92', 795 | value=12345678, ) 796 | gui.Label(name='label_356_21_155', height='17', left='467', 797 | top='130', width='60', text=u'Fecha:', ) 798 | gui.TextBox(id=290, mask='date', name='fecha_cbte', left='517', top='125', 799 | width='122', 800 | value=datetime.date(2014, 5, 27), ) 801 | with gui.Panel(label=u'Conceptos a incluir', name='conceptos', 802 | height='89', left='8', top='157', width='265', 803 | image='', ): 804 | gui.CheckBox(label=u'Productos', name='productos', left='13', 805 | top='24', width='99', 806 | ) 807 | gui.CheckBox(label=u'Servicios', name='servicios', left='132', 808 | top='24', width='110', value=True, 809 | onclick=on_servicios_change, 810 | ) 811 | gui.Label(name='label_182_163', height='25', left='11', 812 | top='55', width='42', text=u'Forma Pago:', ) 813 | gui.ComboBox(name='forma_pago', value=u'Contado', 814 | left='111', top='50', width='145', 815 | items=[u"Contado", u"Tarjeta de Débito", 816 | u"Tarjeta de Crédito", u"Cuenta Corriente", 817 | u"Cheque", u"Ticket", u"Otra"] ) 818 | with gui.Panel(label=u'Per\xedodo Facturado', name='periodo', 819 | height='89', left='276', top='158', width='363', 820 | image='', ): 821 | gui.Label(name='label_272_30_1442_1458', height='17', 822 | left='17', top='25', width='49', text=u'Desde:', ) 823 | gui.Label(name='label_272_30', left='192', top='25', width='49', 824 | text=u'Hasta:', ) 825 | gui.TextBox(id=998, mask='date', name='fecha_desde', 826 | left='72', top='20', width='113', 827 | value=datetime.date(2014, 5, 28), ) 828 | gui.TextBox(mask='date', name='fecha_hasta', left='240', top='20', 829 | width='113', 830 | value=datetime.date(2014, 5, 28), ) 831 | gui.Label(name='label_272_30_1442', height='17', left='113', 832 | top='57', width='49', text=u'Vto. para el Pago:', ) 833 | gui.TextBox(mask='date', name='fecha_venc_pago', 834 | left='241', top='51', width='113', 835 | value=datetime.date(2014, 5, 28), ) 836 | with gui.Notebook(name='notebook', height='197', left='7', 837 | top='249', width='631', selection=0, ): 838 | with gui.TabPanel(name='tab_art', selected=True, 839 | text=u'Art\xedculos', ): 840 | with gui.GridView(name=u'items', height='118', left='10', 841 | top='6', width='610', row_label="", 842 | ongridcellchanged=on_grid_cell_change): 843 | gui.GridColumn(align='right', name=u'qty', type='double', 844 | format="4,2", represent=u'%0.2f', 845 | text=u'Cant.', width=50, ) 846 | gui.GridColumn(name=u'codigo', represent='%s', type='text', 847 | text=u'C\xf3digo', width=75, ) 848 | gui.GridColumn(name=u'ds', represent='%s', type='combo', 849 | text=u'Descripci\xf3n', width=275, ) 850 | gui.GridColumn(align='right', name=u'precio', type='double', 851 | format="11,2", represent=u'%0.2f', text=u'Precio', 852 | width=75, ) 853 | gui.GridColumn(align='center', name=u'iva_id', represent='%s', 854 | choices={1: "no gravado", 2: "exento", 855 | 3: "0%", 4: "10.5%", 5: "21%" , 856 | 6: "27%", 8: "5%", 9: "2.5%"}, 857 | text=u'IVA', type='choice', width=50, ) 858 | gui.GridColumn(align='right', name=u'subtotal', type='double', 859 | represent=u'%0.2f', text=u'Subtotal', 860 | width=75, format="15,2") 861 | gui.Button(label=u'Agregar', name='agregar', left='6', 862 | top='127', width='85px', onclick=on_agregar_click) 863 | gui.Button(id=493, label=u'Borrar', name='borrar', 864 | left='94', top='127', width='85px', onclick= on_borrar_click) 865 | gui.Button(label=u'Modificar', name='modificar', left='183', 866 | top='128', width='85px', visible=False) 867 | with gui.TabPanel(name='alicuotas_iva', selected=False, 868 | text=u'Al\xedcuotas IVA', ): 869 | with gui.ListView(name='listado', height='100', 870 | left='15', top='34', width='357', item_count=0, 871 | sort_column=1, ): 872 | gui.ListColumn(name=u'iva_id', text=u'ID', width=40, 873 | represent=lambda x: {3: "0%", 4: "10.5%", 874 | 5: "21%", 6: "27%", 875 | 8: "5%", 9: "2.5%"}[x]) 876 | gui.ListColumn(name=u'alicuota', text=u'Al\xedcuota', 877 | align="right", width=75, represent="%.2f") 878 | gui.ListColumn(name=u'base_imp', text=u'Base Imp.', 879 | width=100, represent="%.2f", align="right") 880 | gui.ListColumn(name=u'importe', text=u'Importe IVA', 881 | width=100, represent="%.2f", align="right") 882 | gui.Label(name='label_388', left='20', top='11', 883 | text=u'Subtotales de IVA liquidado por al\xedcuota:', ) 884 | gui.Label(name='label_387_630', height='17', left='393', 885 | top='71', width='92', text=u'No Gravado:', ) 886 | gui.TextBox(name='imp_tot_conc', left='519', top='67', width='92', 887 | mask='#############.##', alignment='right', editable=False) 888 | gui.Label(name='label_387_542', height='17', left='393', 889 | top='40', width='99', text=u'Neto Gravado:', ) 890 | gui.TextBox(name='imp_neto', mask='#############.##', alignment='right', 891 | left='519', top='36', width='92', editable=False) 892 | gui.Label(name='label_387', left='395', top='100', 893 | text=u'Exento:', ) 894 | gui.TextBox(name='imp_op_ex', mask='#############.##', alignment='right', 895 | left='519', top='97', width='92', editable=False) 896 | with gui.TabPanel(id=869, name='tributos', selected=False, 897 | text=u'Otros tributos', ): 898 | with gui.GridView(name='grilla', height='102', 899 | left='12', top='18', width='606', item_count=0, 900 | ongridcellchanged=recalcular, 901 | sort_column=0, ): 902 | gui.GridColumn(name='tributo_id', text=u'Impuesto', 903 | choices={1: "nacional", 2: "provincial", 904 | 3: "municipal", 4: "interno", 905 | 99: "otro"}, 906 | type='choice', width=125, ) 907 | gui.GridColumn(name='desc', text=u'Descripci\xf3n', 908 | width=200, type='text', ) 909 | gui.GridColumn(name='base_imp', text=u'Base Imp.', 910 | width=75, type='double', format='15,2',) 911 | gui.GridColumn(name='alic', text=u'Al\xedcuota', width=75, 912 | type='double', format='3,2',) 913 | gui.GridColumn(name='importe', text=u'Importe', width=125, 914 | type='double', format='15,2',) 915 | gui.Button(label=u'Agregar', name='agregar', left='6', 916 | top='127', width='85px', 917 | onclick=on_agregar_tributo_click) 918 | gui.Button(id=493, label=u'Borrar', name='borrar', 919 | left='94', top='127', width='85px', 920 | onclick=on_borrar_tributo_click) 921 | gui.Button(label=u'Modificar', name='modificar', left='183', 922 | top='128', width='85px', visible=False) 923 | with gui.TabPanel(name='obs', selected=False, 924 | text=u'Observaciones', ): 925 | gui.Label(name='label_1324', left='15', top='65', 926 | text=u'Obs. Comerciales:', ) 927 | gui.Label(id=1938, name='label_1324_1938', height='17', left='14', 928 | top='15', width='106', text=u'Obs. Generales:', ) 929 | gui.Label(name='label_1325', left='15', top='110', 930 | text=u'Obs. AFIP:', ) 931 | gui.TextBox(name='generales', multiline=True, 932 | height='45', left='147', top='10', width='467', 933 | ) 934 | gui.TextBox(name='comerciales', multiline=True, 935 | height='45', left='147', top='60', width='468', 936 | ) 937 | gui.TextBox(name='afip', multiline=True, 938 | height='45', left='147', top='110', width='468', 939 | ) 940 | with gui.Panel(label=u'Autorizaci\xf3n AFIP:', name='aut', 941 | height='121', left='8', top='449', width='335', 942 | image='', ): 943 | gui.Label(name='label_26_372_2499_2861', height='17', 944 | left='8', top='28', width='39', text=u'CAE:', ) 945 | gui.TextBox(name='cae', left='70', top='23', width='133', 946 | value=u'123456789012345', editable=False) 947 | gui.Label(name='label_26_372_217', height='17', left='8', 948 | top='60', width='71', text=u'Venc. CAE:', ) 949 | gui.TextBox(mask='date', name=u'fecha_vto_cae', 950 | alignment='center', left='80', top='54', 951 | value=datetime.date(2014, 2, 11), editable=False) 952 | gui.Button(label=u'Obtener', name=u'obtener', left='224', 953 | top='21', width='75', onclick=obtener_cae) 954 | gui.Label(name='label_26_372', left='11', top='90', width='39', 955 | text=u'Resultado:', ) 956 | gui.RadioButton(label=u'Aceptado', name='aceptado', 957 | left='95', top='88', width='75', 958 | value=True, enabled=False) 959 | gui.RadioButton(label=u'Rechazado', name='rechazado', 960 | left='199', top='88', width='100', 961 | enabled=False) 962 | gui.Button(label=u'Imprimir', name=u'imprimir', 963 | left='200', top='53', width='70', onclick=generar_pdf) 964 | gui.Button(label=u'Enviar', name=u'enviar', left='270', top='53', 965 | width='55', onclick=enviar) 966 | 967 | gui.Label(name='label_469_345_1892', alignment='right', 968 | height='17', left='466', top='488', width='50', 969 | text=u'IVA:', ) 970 | gui.TextBox(mask='#############.##', name=u'imp_iva', editable=False, 971 | alignment='right', left='520', top='485', width='115',) 972 | gui.Label(name='label_469_345', alignment='right', height='17', 973 | left='466', top='461', width='50', 974 | text=u'Tributos:', ) 975 | gui.TextBox(mask='#############.##', name=u'imp_trib', editable=False, 976 | alignment='right', left='520', top='455', width='115') 977 | gui.Label(name='label_469_345_226', alignment='right', 978 | height='17', left='480', top='519', width='36', 979 | text=u'Total:', ) 980 | gui.TextBox(mask='#############.##', name=u'imp_total', alignment='right', 981 | left='520', top='515', width='115', editable=False) 982 | gui.Image(name='image_507_571', height='27', left='350', top='514', 983 | width='178', stretch=False, filename='sistemas-agiles.png', ) 984 | gui.Image(name='image_33_540', height='37', left='350', top='460', 985 | width='75', stretch=False, filename='logo-pyafipws.png', ) 986 | gui.Button(label=u'Grabar', name=u'grabar', 987 | left='350', top='542', width='75', onclick=grabar) 988 | gui.Button(label=u'Limpiar', name=u'limpiar', left='430', top='542', 989 | width='75', onclick=lambda evt: limpiar(evt, True)) 990 | gui.Button(label=u'Cargar', name=u'cargar', left='510', top='542', 991 | width='75', onclick=cargar) 992 | 993 | 994 | # --- gui2py designer generated code ends --- 995 | 996 | 997 | # obtener referencia a la ventana principal: 998 | mywin = gui.get("mywin") 999 | panel = mywin['panel'] 1000 | grilla = panel['notebook']['tab_art']['items'] 1001 | 1002 | 1003 | if __name__ == "__main__": 1004 | try: 1005 | if len(sys.argv)>1 and not sys.argv[1].startswith("-"): 1006 | CONFIG_FILE = sys.argv[1] 1007 | new_conf = os.path.join("conf", CONFIG_FILE) 1008 | if not os.path.exists(CONFIG_FILE) and os.path.exists(new_conf): 1009 | CONFIG_FILE = new_conf 1010 | config = SafeConfigParser() 1011 | config.read(CONFIG_FILE) 1012 | if not len(config.sections()): 1013 | if os.path.exists(CONFIG_FILE): 1014 | gui.alert(u"Error al cargar configuración: %s" % CONFIG_FILE) 1015 | else: 1016 | gui.alert(u"No existe archivo de configuración: %s" % CONFIG_FILE) 1017 | sys.exit(1) 1018 | cert = config.get('WSAA','CERT') 1019 | privatekey = config.get('WSAA','PRIVATEKEY') 1020 | cuit_emisor = config.get('WSFEv1','CUIT') 1021 | cat_iva_emisor = int(config.get('WSFEv1','CAT_IVA')) # 1: RI 1022 | pto_vta_emisor = int(config.get('WSFEv1','PTO_VTA')) 1023 | if config.has_option('WSFEv1','IVA_INCLUIDO'): 1024 | iva_incluido = config.get('WSFEv1','IVA_INCLUIDO').lower() == "true" 1025 | else: 1026 | iva_incluido = False 1027 | 1028 | if config.has_section('FACTURA'): 1029 | conf_fact = dict(config.items('FACTURA')) 1030 | else: 1031 | conf_fact = {} 1032 | 1033 | conf_pdf = dict(config.items('PDF')) 1034 | conf_mail = dict(config.items('MAIL')) 1035 | 1036 | if config.has_option('WSAA','URL') and not HOMO: 1037 | wsaa_url = config.get('WSAA','URL') 1038 | else: 1039 | wsaa_url = None 1040 | 1041 | if config.has_option('WSFEv1','URL') and not HOMO: 1042 | wsfev1_url = config.get('WSFEv1','URL') 1043 | else: 1044 | wsfev1_url = None 1045 | 1046 | if config.has_option('WSFEXv1','URL') and not HOMO: 1047 | wsfexv1_url = config.get('WSFEXv1','URL') 1048 | else: 1049 | wsfexv1_url = None 1050 | 1051 | if config.has_option('WS-SR-PADRON-A4','URL') and not HOMO: 1052 | ws_sr_padron_a4_url = config.get('WS-SR-PADRON-A4', 'URL') 1053 | else: 1054 | ws_sr_padron_a4_url = None 1055 | 1056 | DEFAULT_WEBSERVICE = "wsfev1" 1057 | if config.has_section('PYRECE'): 1058 | DEFAULT_WEBSERVICE = config.get('PYRECE','WEBSERVICE') 1059 | 1060 | if config.has_section('PROXY'): 1061 | proxy_dict = dict(("proxy_%s" % k,v) for k,v in config.items('PROXY')) 1062 | proxy_dict['proxy_port'] = int(proxy_dict['proxy_port']) 1063 | else: 1064 | proxy_dict = {} 1065 | 1066 | id_factura = None 1067 | 1068 | import datos 1069 | 1070 | # inicializo los componenetes de negocio: 1071 | 1072 | padron = PadronAFIP() 1073 | sired = SIRED() 1074 | wsaa = WSAA() 1075 | wsfev1 = WSFEv1() 1076 | ta = wsaa.Autenticar("wsfe", cert, privatekey, wsaa_url, cache="cache") 1077 | if not ta: 1078 | gui.alert(wsaa.Excepcion, u"Imposible autenticar con WSAA AFIP") 1079 | else: 1080 | wsfev1.SetTicketAcceso(ta) 1081 | wsfev1.Cuit = cuit_emisor 1082 | wsfev1.Conectar("cache", wsfev1_url) 1083 | # intento autenticar el servicio de padron, se usa si está disponible 1084 | ta = wsaa.Autenticar("ws_sr_padron_a4", cert, privatekey, wsaa_url, cache="cache") 1085 | if ta: 1086 | padron_a4 = WSSrPadronA4() 1087 | padron_a4.LanzarExcepciones = True 1088 | padron_a4.SetTicketAcceso(ta) 1089 | padron_a4.Cuit = cuit_emisor 1090 | padron_a4.Conectar("cache", ws_sr_padron_a4_url) 1091 | else: 1092 | padron_a4 = None 1093 | 1094 | fepdf = FEPDF() 1095 | # cargo el formato CSV por defecto (factura.csv) 1096 | fepdf.CargarFormato(conf_fact.get("formato", "factura.csv")) 1097 | # establezco formatos (cantidad de decimales) según configuración: 1098 | fepdf.FmtCantidad = conf_fact.get("fmt_cantidad", "0.2") 1099 | fepdf.FmtPrecio = conf_fact.get("fmt_precio", "0.2") 1100 | # configuración general del PDF: 1101 | fepdf.CUIT = cuit_emisor 1102 | 1103 | fepdf.AgregarCampo("draft", 'T', 100, 250, 0, 0, 1104 | size=70, rotate=45, foreground=0x808080, priority=-1) 1105 | 1106 | # ajustar opciones de articulos: 1107 | if config.has_section('ARTICULOS'): 1108 | datos.articulos = dict([(k, unicode(v, "latin1", "replace")) 1109 | for k,v in config.items('ARTICULOS')]) 1110 | grilla.columns[2].choices = datos.articulos.values() 1111 | limpiar(None) 1112 | 1113 | # agrego item de ejemplo: 1114 | if '--prueba' in sys.argv: 1115 | panel['cliente']['nro_doc'].value = "20-00000051-6" 1116 | if padron_a4: 1117 | on_nro_doc_change(None) 1118 | panel['cliente']['cat_iva'].value = 1 1119 | grilla.items.append({'qty': 1, 'codigo': '1111', 1120 | 'ds': u"Honorarios p/administración de alquileres", 'precio': 1000., 1121 | 'iva_id': 5, 'subtotal': 1210.}) 1122 | recalcular() 1123 | 1124 | mywin.show() 1125 | gui.main_loop() 1126 | except: 1127 | ex = traceback.format_exception(sys.exc_type, sys.exc_value, sys.exc_traceback) 1128 | msg = ''.join(ex) 1129 | print msg 1130 | gui.alert(msg, 'Excepcion') 1131 | --------------------------------------------------------------------------------