From 9e50033b9f70a6b7198b868da3df5d8508d3b025 Mon Sep 17 00:00:00 2001 From: Mauricio Baeza Date: Sun, 29 Oct 2017 16:53:10 -0600 Subject: [PATCH] Cancelar facturas con xml firmado --- source/app/controllers/pac.py | 10 +++-- source/app/controllers/util.py | 49 ++++++++++++++++++++++++- source/app/models/db.py | 3 ++ source/app/models/main.py | 32 +++++++++++++--- source/app/settings.py | 4 ++ source/static/js/controller/invoices.js | 19 +++++++++- source/templates/cancel_template.xml | 28 ++++++++++++++ 7 files changed, 135 insertions(+), 10 deletions(-) create mode 100644 source/templates/cancel_template.xml diff --git a/source/app/controllers/pac.py b/source/app/controllers/pac.py index 89c37a3..b99fb87 100644 --- a/source/app/controllers/pac.py +++ b/source/app/controllers/pac.py @@ -371,7 +371,7 @@ class Finkok(object): if os.path.isfile(file_xml): root = etree.parse(file_xml).getroot() else: - root = etree.fromstring(file_xml) + root = etree.fromstring(file_xml.encode()) xml = etree.tostring(root) @@ -385,8 +385,12 @@ class Finkok(object): 'store_pending': True, } - result = client.service.cancel_signature(**args) - return result + try: + result = client.service.cancel_signature(**args) + return result + except Fault as e: + self.error = str(e) + return '' def get_acuse(self, rfc, uuids, type_acuse='C'): for u in uuids: diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py index 275a4f6..318fe6f 100644 --- a/source/app/controllers/util.py +++ b/source/app/controllers/util.py @@ -33,7 +33,8 @@ from dateutil import parser from .helper import CaseInsensitiveDict, NumLet, SendMail, TemplateInvoice from settings import DEBUG, log, template_lookup, COMPANIES, DB_SAT, \ - PATH_XSLT, PATH_XSLTPROC, PATH_OPENSSL, PATH_TEMPLATES, PATH_MEDIA, PRE + PATH_XSLT, PATH_XSLTPROC, PATH_OPENSSL, PATH_TEMPLATES, PATH_MEDIA, PRE, \ + PATH_XMLSEC, TEMPLATE_CANCEL #~ def _get_hash(password): @@ -1048,6 +1049,52 @@ def get_date(value, next_day=False): return d +def cancel_cfdi(uuid, pk12, rfc, auth): + from .pac import Finkok as PAC + + template = read_file(TEMPLATE_CANCEL, 'r') + data = { + 'rfc': rfc, + 'fecha': datetime.datetime.now().isoformat()[:19], + 'uuid': str(uuid).upper(), + } + template = template.format(**data) + + data = { + 'xmlsec': PATH_XMLSEC, + 'pk12': _save_temp(pk12), + 'pass': _get_md5(rfc), + 'template': _save_temp(template, 'w'), + } + args = '"{xmlsec}" --sign --pkcs12 "{pk12}" --pwd {pass} ' \ + '"{template}"'.format(**data) + xml_sign = _call(args) + + if DEBUG: + auth = {} + else: + if not auth: + msg = 'Sin datos para cancelar' + result = {'ok': False, 'error': msg} + return result + + msg = 'Factura cancelada correctamente' + data = {'ok': True, 'msg': msg, 'row': {'estatus': 'Cancelada'}} + pac = PAC(auth) + result = pac.cancel_signature(xml_sign) + if result: + codes = {None: '', + 'Could not get UUID Text': 'UUID no encontrado'} + if not result['CodEstatus'] is None: + data['ok'] = False + data['msg'] = codes.get(result['CodEstatus'], result['CodEstatus']) + else: + data['ok'] = False + data['msg'] = pac.error + + return data, result + + class ImportFacturaLibre(object): def __init__(self, path): diff --git a/source/app/models/db.py b/source/app/models/db.py index 9cf565c..ba042c8 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -32,6 +32,9 @@ class StorageEngine(object): def send_email(self, values, session): return main.Facturas.send(values['id'], session['rfc']) + def _get_cancelinvoice(self, values): + return main.Facturas.cancel(values['id']) + def _get_statussat(self, values): return main.Facturas.get_status_sat(values['id']) diff --git a/source/app/models/main.py b/source/app/models/main.py index 654ac54..cb8e79f 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -723,7 +723,7 @@ class Socios(BaseModel): .where((Socios.id==id) & (Socios.es_cliente==True)) .dicts() ) - print (id, row) + #~ print (id, row) if len(row): return {'ok': True, 'row': row[0]} return {'ok': False} @@ -1009,6 +1009,8 @@ class Facturas(BaseModel): notas = TextField(default='') pagada = BooleanField(default=False) cancelada = BooleanField(default=False) + fecha_cancelacion = DateTimeField(null=True) + acuse = TextField(default='') donativo = BooleanField(default=False) tipo_relacion = TextField(default='') error = TextField(default='') @@ -1016,6 +1018,25 @@ class Facturas(BaseModel): class Meta: order_by = ('fecha',) + @classmethod + def cancel(cls, id): + msg = 'Factura cancelada correctamente' + auth = Emisor.get_auth() + certificado = Certificado.select()[0] + obj = Facturas.get(Facturas.id==id) + data, result = util.cancel_cfdi( + obj.uuid, certificado.p12, certificado.rfc, auth) + if data['ok']: + obj.estatus = 'Cancelada' + obj.error = '' + obj.cancelada = True + obj.fecha_cancelacion = result['Fecha'] + obj.acuse = result['Acuse'] + else: + obj.error = data['msg'] + obj.save() + return data + @classmethod def filter_years(cls): data = [{'id': -1, 'value': 'Todos'}] @@ -1025,7 +1046,8 @@ class Facturas(BaseModel): .order_by(Facturas.fecha.year) .scalar(as_tuple=True) ) - data += [{'id': int(row), 'value': int(row)} for row in rows] + if not rows is None: + data += [{'id': int(row), 'value': int(row)} for row in rows] return tuple(data) @classmethod @@ -1489,7 +1511,7 @@ class Facturas(BaseModel): auth = Emisor.get_auth() - error = False + #~ error = False msg = 'Factura timbrada correctamente' result = util.timbra_xml(obj.xml, auth) if result['ok']: @@ -1498,10 +1520,10 @@ class Facturas(BaseModel): obj.fecha_timbrado = result['fecha'] obj.estatus = 'Timbrada' obj.error = '' - obj.save() + #~ obj.save() row = {'uuid': obj.uuid, 'estatus': 'Timbrada'} else: - error = True + #~ error = True msg = result['error'] obj.estatus = 'Error' obj.error = msg diff --git a/source/app/settings.py b/source/app/settings.py index 1776135..ad1d0cf 100644 --- a/source/app/settings.py +++ b/source/app/settings.py @@ -25,6 +25,8 @@ DB_SAT = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', 'sat.db')) IV = 'valores_iniciales.json' INIT_VALUES = os.path.abspath(os.path.join(BASE_DIR, '..', 'db', IV)) +CT = 'cancel_template.xml' +TEMPLATE_CANCEL = os.path.abspath(os.path.join(PATH_TEMPLATES, CT)) PATH_XSLT = os.path.abspath(os.path.join(BASE_DIR, '..', 'xslt')) PATH_BIN = os.path.abspath(os.path.join(BASE_DIR, '..', 'bin')) @@ -69,9 +71,11 @@ log = Logger(LOG_NAME) PATH_XSLTPROC = 'xsltproc' PATH_OPENSSL = 'openssl' +PATH_XMLSEC = 'xmlsec1' if 'win' in sys.platform: PATH_XSLTPROC = os.path.join(PATH_BIN, 'xsltproc.exe') PATH_OPENSSL = os.path.join(PATH_BIN, 'openssl.exe') + PATH_XMLSEC = os.path.join(PATH_BIN, 'xmlsec.exe') PRE = { diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js index 29dd002..91b6ae2 100644 --- a/source/static/js/controller/invoices.js +++ b/source/static/js/controller/invoices.js @@ -695,7 +695,19 @@ function grid_invoices_click(id, e, node){ function send_cancel(id){ - show(id) + webix.ajax().get('/values/cancelinvoice', {id: id}, function(text, data){ + var values = data.json() + if(values.ok){ + msg_sucess(values.msg) + gi.updateItem(id, values.row) + }else{ + webix.alert({ + title: 'Error al Cancelar', + text: values.msg, + type: 'alert-error' + }) + } + }) } function cmd_invoice_cancelar_click(){ @@ -715,6 +727,11 @@ function cmd_invoice_cancelar_click(){ return } + if(row.estatus == 'Cancelada'){ + msg_error('La factura ya esta cancelada') + return + } + msg = '¿Estás seguro de enviar a cancelar esta factura?

\ ESTA ACCIÓN NO SE PUEDE DESHACER' webix.confirm({ diff --git a/source/templates/cancel_template.xml b/source/templates/cancel_template.xml new file mode 100644 index 0000000..6b58286 --- /dev/null +++ b/source/templates/cancel_template.xml @@ -0,0 +1,28 @@ + + + + {uuid} + + + + + + + + + + + + + + + + + + + + + + + +