From b997a4703cbf26fdd9a04242ad2a8f360575db46 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Thu, 9 Nov 2017 23:51:54 -0600 Subject: [PATCH 1/6] Generar PDF 3.2 --- source/app/controllers/util.py | 20 +++++++++++++++----- source/app/models/main.py | 4 ++++ 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py index 70266d7..9d0bfb9 100644 --- a/source/app/controllers/util.py +++ b/source/app/controllers/util.py @@ -853,7 +853,7 @@ def to_letters(value, moneda): 'USD': 'dólar', 'EUR': 'euro', } - return NumLet(value, monedas[moneda]).letras + return NumLet(value, monedas.get(moneda, moneda)).letras def get_qr(data): @@ -892,12 +892,18 @@ def _comprobante(values, options): def _emisor(doc, version, values): - node = doc.find('{}Emisor'.format(PRE[version])) - data = CaseInsensitiveDict(node.attrib.copy()) - node = node.find('{}DomicilioFiscal'.format(PRE[version])) + emisor = doc.find('{}Emisor'.format(PRE[version])) + data = CaseInsensitiveDict(emisor.attrib.copy()) + node = emisor.find('{}DomicilioFiscal'.format(PRE[version])) if not node is None: data.update(CaseInsensitiveDict(node.attrib.copy())) - data['regimenfiscal'] = values['regimenfiscal'] + + if version == '3.2': + node = emisor.find('{}RegimenFiscal'.format(PRE[version])) + if not node is None: + data['regimenfiscal'] = node.attrib['Regimen'] + else: + data['regimenfiscal'] = values['regimenfiscal'] path = _join(PATH_MEDIA, 'logos', '{}.png'.format(data['rfc'].lower())) if is_file(path): @@ -912,6 +918,10 @@ def _receptor(doc, version, values): node = node.find('{}Domicilio'.format(PRE[version])) if not node is None: data.update(node.attrib.copy()) + + if version == '3.2': + return data + data['usocfdi'] = values['usocfdi'] return data diff --git a/source/app/models/main.py b/source/app/models/main.py index 6048a89..c0825e8 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -1248,6 +1248,10 @@ class Facturas(BaseModel): def _get_not_in_xml(self, invoice): values = {} + + if invoice.version == '3.2': + return values + obj = SATRegimenes.get(SATRegimenes.key==invoice.regimen_fiscal) values['regimenfiscal'] = str(obj) From f1dff39e1b3e09aed7e7fe74f0d6410c931b1b9d Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sat, 11 Nov 2017 11:17:49 -0600 Subject: [PATCH 2/6] =?UTF-8?q?Validar=20configuracion=20m=C3=ADnima=20par?= =?UTF-8?q?a=20facturar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- source/app/controllers/util.py | 25 ++++++++++++-- source/app/models/db.py | 3 ++ source/app/models/main.py | 44 +++++++++++++++++++++++-- source/static/js/controller/admin.js | 4 +-- source/static/js/controller/invoices.js | 13 ++++++++ 5 files changed, 82 insertions(+), 7 deletions(-) diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py index 9d0bfb9..7e16b57 100644 --- a/source/app/controllers/util.py +++ b/source/app/controllers/util.py @@ -1192,25 +1192,46 @@ def get_bool(value): class ImportFacturaLibre(object): - def __init__(self, path): + def __init__(self, path, rfc): + self._rfc = rfc self._con = None self._cursor = None self._is_connect = self._connect(path) self._clientes = [] self._clientes_rfc = [] + self._error = '' + + @property + def error(self): + return self._error @property def is_connect(self): return self._is_connect + def _validate_rfc(self): + sql = "SELECT rfc FROM emisor LIMIT 1" + self._cursor.execute(sql) + obj = self._cursor.fetchone() + if obj is None: + self._error = 'No se encontró al emisor: {}'.format(self._rfc) + return False + + if obj['rfc'] != self._rfc: + self._error = 'Los datos no corresponden al emisor: {}'.format(self._rfc) + return False + + return True + def _connect(self, path): try: self._con = sqlite3.connect(path) self._con.row_factory = sqlite3.Row self._cursor = self._con.cursor() - return True + return self._validate_rfc() except Exception as e: log.error(e) + self._error = 'No se pudo conectar a la base de datos' return False def close(self): diff --git a/source/app/models/db.py b/source/app/models/db.py index c53fdfc..b7db743 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -14,6 +14,9 @@ class StorageEngine(object): def get_values(self, table, values=None): return getattr(self, '_get_{}'.format(table))(values) + def _get_validartimbrar(self, values): + return main.validar_timbrar() + def _get_preproductos(self, values): return main.PreFacturasDetalle.facturar(values['id']) diff --git a/source/app/models/main.py b/source/app/models/main.py index c0825e8..c37721a 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -67,6 +67,40 @@ def upload_file(rfc, opt, file_obj): return result +def validar_timbrar(): + try: + obj = Emisor.select()[0] + except IndexError: + msg = 'Es necesario agregar los datos del emisor' + return {'ok': False, 'msg': msg} + + try: + obj = Folios.select()[0] + except IndexError: + msg = 'Es necesaria al menos una serie de folios' + return {'ok': False, 'msg': msg} + + msg = 'Es necesario configurar un certificado de sellos' + try: + obj = Certificado.select()[0] + except IndexError: + return {'ok': False, 'msg': msg} + + if not obj.serie: + return {'ok': False, 'msg': msg} + + dias = obj.hasta - util.now() + if dias.days < 0: + msg = 'El certificado ha vencido, es necesario cargar uno nuevo' + return {'ok': False, 'msg': msg} + + msg = '' + if dias.days < 15: + msg = 'El certificado vence en: {} días.'.format(dias.days) + + return {'ok': True, 'msg': msg} + + class Configuracion(BaseModel): clave = TextField(unique=True) valor = TextField(default='') @@ -288,7 +322,11 @@ class Emisor(BaseModel): @classmethod def get_regimenes(cls): - obj = Emisor.select()[0] + try: + obj = Emisor.select()[0] + except IndexError: + return () + rows = [{'id': row.key, 'value': row.name} for row in obj.regimenes] return tuple(rows) @@ -2699,9 +2737,9 @@ def _importar_factura_libre(archivo): conectar(args) log.info('Importando datos...') - app = util.ImportFacturaLibre(archivo) + app = util.ImportFacturaLibre(archivo, rfc) if not app.is_connect: - log.error('\tNo se pudo conectar a la base de datos') + log.error('\t{}'.format(app.error)) return data = app.import_data() diff --git a/source/static/js/controller/admin.js b/source/static/js/controller/admin.js index 130e675..a89afa3 100644 --- a/source/static/js/controller/admin.js +++ b/source/static/js/controller/admin.js @@ -392,9 +392,9 @@ function cmd_subir_certificado_click(){ return } - var rfc = $$('form_cert').getValues()['cert_rfc'] + var serie = $$('form_cert').getValues()['cert_serie'] - if(rfc){ + if(serie){ msg = 'Ya existe un certificado guardado

¿Deseas reemplazarlo?' webix.confirm({ title: 'Certificado Existente', diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js index 9bf0193..f486f06 100644 --- a/source/static/js/controller/invoices.js +++ b/source/static/js/controller/invoices.js @@ -95,6 +95,19 @@ function default_config(){ get_regimen_fiscal() table_pt.clear() table_totals.clear() + + webix.ajax().sync().get('/values/validartimbrar', function(text, data){ + var values = data.json() + if(!values.ok){ + msg_error(values.msg) + $$('cmd_timbrar').disable() + }else{ + if(values.msg){ + msg_error(values.msg) + } + $$('cmd_timbrar').enable() + } + }) } From 6f30d42d4064e9dff18e3282bd1787dd8d04a47e Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sat, 11 Nov 2017 11:40:55 -0600 Subject: [PATCH 3/6] Cargar plantillas ODS --- source/app/controllers/util.py | 9 ++++++ source/static/js/controller/admin.js | 41 +++++++++++++++++++++++++++- 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py index 7e16b57..616dfbb 100644 --- a/source/app/controllers/util.py +++ b/source/app/controllers/util.py @@ -1121,6 +1121,15 @@ def upload_file(rfc, opt, file_obj): name = '{}_3.3.ods'.format(rfc.lower()) path = _join(PATH_MEDIA, 'templates', name) + elif opt == 'txt_plantilla_factura_32': + tmp = file_obj.filename.split('.') + ext = tmp[-1].lower() + if ext != 'ods': + msg = 'Extensión de archivo incorrecta, selecciona un archivo ODS' + return {'status': 'server', 'name': msg, 'ok': False} + + name = '{}_3.2.ods'.format(rfc.lower()) + path = _join(PATH_MEDIA, 'templates', name) if save_file(path, file_obj.file.read()): return {'status': 'server', 'name': file_obj.filename, 'ok': True} diff --git a/source/static/js/controller/admin.js b/source/static/js/controller/admin.js index a89afa3..408e280 100644 --- a/source/static/js/controller/admin.js +++ b/source/static/js/controller/admin.js @@ -29,6 +29,7 @@ var controllers = { //~ Opciones tb_options = $$('tab_options').getTabbar() tb_options.attachEvent('onChange', tab_options_change) + $$('txt_plantilla_factura_32').attachEvent('onItemClick', txt_plantilla_factura_32_click) $$('txt_plantilla_factura_33').attachEvent('onItemClick', txt_plantilla_factura_33_click) } } @@ -686,7 +687,7 @@ function txt_plantilla_factura_33_click(e){ id: 'win_template', modal: true, position: 'center', - head: 'Subir Plantilla', + head: 'Subir Plantilla 3.3 ODT', body: { view: 'form', elements: body_elements, @@ -706,6 +707,44 @@ function txt_plantilla_factura_33_click(e){ } +function txt_plantilla_factura_32_click(e){ + + var body_elements = [ + {cols: [{width: 100}, {view: 'uploader', id: 'up_template', autosend: true, link: 'lst_files', + value: 'Seleccionar archivo', upload: '/files/txt_plantilla_factura_32', + width: 200}, {width: 100}]}, + {view: 'list', id: 'lst_files', type: 'uploader', autoheight:true, + borderless: true}, + {}, + {cols: [{}, {view: 'button', label: 'Cerrar', autowidth: true, + click:("$$('win_template').close();")}, {}]} + ] + + var w = webix.ui({ + view: 'window', + id: 'win_template', + modal: true, + position: 'center', + head: 'Subir Plantilla 3.2 ODT', + body: { + view: 'form', + elements: body_elements, + } + }) + + w.show() + + $$('up_template').attachEvent('onUploadComplete', function(response){ + if(response.ok){ + $$('txt_plantilla_factura_32').setValue(response.name) + msg_sucess('Plantilla cargada correctamente') + }else{ + msg_error(response.name) + } + }) +} + + function tab_options_change(nv, ov){ var cv = { Plantillas: 'templates', From 2e06083e29dbc0041529e2952dcaccdf5948aec0 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sat, 11 Nov 2017 13:42:51 -0600 Subject: [PATCH 4/6] Generar PDF de CFDI 3.2 --- source/app/controllers/util.py | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py index 616dfbb..9637b32 100644 --- a/source/app/controllers/util.py +++ b/source/app/controllers/util.py @@ -4,6 +4,7 @@ import datetime import getpass import hashlib import json +import locale import mimetypes import os import re @@ -883,11 +884,31 @@ def _comprobante(values, options): data['condicionesdepago'] = \ 'Condiciones de pago: {}'.format(data['condicionesdepago']) data['moneda'] = options['moneda'] + else: + fields = { + 'formaDePago': 'Forma de Pago: {}\n', + 'metodoDePago': 'Método de pago: {}\n', + 'condicionesDePago': 'Condiciones de Pago: {}\n', + 'NumCtaPago': 'Número de Cuenta de Pago: {}\n', + 'Moneda': 'Moneda: {}\n', + 'TipoCambio': 'Tipo de Cambio: {}', + } + datos = '' + for k, v in fields.items(): + if k in data: + datos += v.format(data[k]) + data['datos'] = datos + + fecha = parser.parse(data['fecha']) + try: + locale.setlocale(locale.LC_TIME, "es_MX.UTF-8") + except: + pass + data['fechaformato'] = fecha.strftime('%A, %d de %B de %Y') data['tipocambio'] = 'Tipo de Cambio: $ {:0.2f}'.format( float(data['tipocambio'])) - if 'serie' in data: - data['folio'] = '{}-{}'.format(data['serie'], data['folio']) + return data From a9dcc074270606bd7b2d2b2707547497385e8632 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sat, 11 Nov 2017 14:27:36 -0600 Subject: [PATCH 5/6] Fix - Generar XML sin relacionados --- source/app/controllers/cfdi_xml.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/source/app/controllers/cfdi_xml.py b/source/app/controllers/cfdi_xml.py index fb61c4b..dee3bfa 100644 --- a/source/app/controllers/cfdi_xml.py +++ b/source/app/controllers/cfdi_xml.py @@ -117,7 +117,7 @@ class CFDI(object): return def _relacionados(self, datos): - if not datos['tipo'] or not datos['cfdis']: + if not datos or not datos['tipo'] or not datos['cfdis']: return node_name = '{}:CfdiRelacionados'.format(self._pre) From a5f8b62929f65b5bc96da32731389e0d0ed133a2 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sat, 11 Nov 2017 15:03:20 -0600 Subject: [PATCH 6/6] Mostrar CFDI relacionados en PDF --- source/app/controllers/util.py | 17 ++++++++++++++--- source/app/models/main.py | 6 ++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py index 9637b32..a73090d 100644 --- a/source/app/controllers/util.py +++ b/source/app/controllers/util.py @@ -864,8 +864,17 @@ def get_qr(data): return path -def _comprobante(values, options): - data = CaseInsensitiveDict(values) +def _get_relacionados(doc, version): + node = doc.find('{}CfdiRelacionados'.format(PRE[version])) + if node is None: + return '' + + uuids = ['UUID: {}'.format(n.attrib['UUID']) for n in node.getchildren()] + return '\n'.join(uuids) + + +def _comprobante(doc, options): + data = CaseInsensitiveDict(doc.attrib.copy()) del data['certificado'] data['totalenletras'] = to_letters(float(data['total']), data['moneda']) @@ -884,6 +893,8 @@ def _comprobante(values, options): data['condicionesdepago'] = \ 'Condiciones de pago: {}'.format(data['condicionesdepago']) data['moneda'] = options['moneda'] + data['tiporelacion'] = options.get('tiporelacion', '') + data['relacionados'] = _get_relacionados(doc, data['version']) else: fields = { 'formaDePago': 'Forma de Pago: {}\n', @@ -1052,7 +1063,7 @@ def _timbre(doc, version, values): def get_data_from_xml(invoice, values): data = {'cancelada': invoice.cancelada} doc = parse_xml(invoice.xml) - data['comprobante'] = _comprobante(doc.attrib.copy(), values) + data['comprobante'] = _comprobante(doc, values) version = data['comprobante']['version'] data['emisor'] = _emisor(doc, version, values) data['receptor'] = _receptor(doc, version, values) diff --git a/source/app/models/main.py b/source/app/models/main.py index c37721a..2a353a1 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -1309,6 +1309,12 @@ class Facturas(BaseModel): obj = SATMonedas.get(SATMonedas.key==invoice.moneda) values['moneda'] = str(obj) + if invoice.tipo_relacion: + obj = SATTipoRelacion.get(SATTipoRelacion.key==invoice.tipo_relacion) + values['tiporelacion'] = str(obj) + + print ('\nTR', invoice.tipo_relacion) + return values @classmethod