diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2b8982e..a362c0e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,13 @@
+v 1.45.4 [25-Ene-2022]
+----------------------
+ - Error: Al timbrar carta porte.
+ - Error: Al cancelar con Comercio Digital.
+ - Error: Issue #42
+ - Mejora: Issue #44
+
+* IMPORTANTE: Es necesario subir de nuevo tus certificados de sello, **solo** si timbras con Comercio Digital.
+
+
v 1.45.3 [23-Ene-2022]
----------------------
- Error: El enviar por correo CFDI de pago. Ticket #40
diff --git a/VERSION b/VERSION
index 1a5c268..bdcb000 100644
--- a/VERSION
+++ b/VERSION
@@ -1,2 +1,2 @@
-1.45.3
+1.45.4
diff --git a/source/app/controllers/main.py b/source/app/controllers/main.py
index 7943af5..1b333ed 100644
--- a/source/app/controllers/main.py
+++ b/source/app/controllers/main.py
@@ -2,6 +2,7 @@
import falcon
from middleware import get_template
+from urllib.parse import unquote
class AppEmpresas(object):
@@ -111,6 +112,14 @@ class AppValues(object):
def on_get(self, req, resp, table):
values = req.params
session = req.env['beaker.session']
+
+ if table == 'product':
+ original = values['name']
+ try:
+ values['name'] = unquote(req.query_string.split('=')[1])
+ except Exception as e:
+ values['name'] = original
+
if req.path in ('/values/titlelogin', '/values/empresas'):
req.context['result'] = self._db.get_values(table, values, session)
resp.status = falcon.HTTP_200
@@ -280,6 +289,7 @@ class AppProducts(object):
def on_get(self, req, resp):
values = req.params
user = req.env['beaker.session']['userobj']
+
if 'opt' in values:
req.context['result'] = self._db.products_get(values, user)
else:
diff --git a/source/app/controllers/pacs/cfdi_cert.py b/source/app/controllers/pacs/cfdi_cert.py
index eb5ef5f..2af68e0 100644
--- a/source/app/controllers/pacs/cfdi_cert.py
+++ b/source/app/controllers/pacs/cfdi_cert.py
@@ -28,18 +28,18 @@ class SATCertificate(object):
self._init_values()
self._get_data_cer(cer)
self._get_data_key(key, password)
- if not password:
- self._test()
+ # ~ if not password:
+ # ~ self._test()
- def _test(self):
- key = self._get_key('')
- self._p = TOKEN
- self._key_der = key.private_bytes(
- encoding=serialization.Encoding.DER,
- format=serialization.PrivateFormat.PKCS8,
- encryption_algorithm=serialization.BestAvailableEncryption(self._p.encode())
- )
- return
+ # ~ def _test(self):
+ # ~ key = self._get_key('')
+ # ~ self._p = ''
+ # ~ self._key_der = key.private_bytes(
+ # ~ encoding=serialization.Encoding.DER,
+ # ~ format=serialization.PrivateFormat.PKCS8,
+ # ~ encryption_algorithm=serialization.BestAvailableEncryption(self._p.encode())
+ # ~ )
+ # ~ return
def _init_values(self):
self._rfc = ''
@@ -52,6 +52,7 @@ class SATCertificate(object):
self._is_fiel = False
self._are_couple = False
self._is_valid_time = False
+ self._key = b''
self._cer = b''
self._cer_pem = ''
self._cer_txt = ''
@@ -110,6 +111,8 @@ class SATCertificate(object):
return
def _get_data_key(self, key, password):
+ self._key = key
+ self._keyp = password
self._key_enc = key
if not key or not password:
return
@@ -175,17 +178,18 @@ class SATCertificate(object):
node = xmlsec.tree.find_node(tree, 'X509Certificate')
node.text = self.cer_txt
- # ~ node = xmlsec.tree.find_node(tree, 'X509IssuerName')
- # ~ node.text = self.issuer
+ node = xmlsec.tree.find_node(tree, 'X509IssuerName')
+ node.text = self.issuer
node = xmlsec.tree.find_node(tree, 'X509SerialNumber')
node.text = self.serial_number
- # ~ node = xmlsec.tree.find_node(tree, 'SignatureValue')
- # ~ node.text = node.text.replace('\n', '')
- # ~ node = xmlsec.tree.find_node(tree, 'Modulus')
- # ~ node.text = node.text.replace('\n', '')
+ node = xmlsec.tree.find_node(tree, 'SignatureValue')
+ node.text = node.text.replace('\n', '')
+ node = xmlsec.tree.find_node(tree, 'Modulus')
+ node.text = node.text.replace('\n', '')
- xml_signed = ET.tostring(tree,
- xml_declaration=True, encoding='UTF-8').decode()
+ # ~ xml_signed = ET.tostring(tree,
+ # ~ xml_declaration=True, encoding='UTF-8').decode()
+ xml_signed = ET.tostring(tree, encoding='UTF-8').decode().replace('\n', '')
return xml_signed
diff --git a/source/app/controllers/pacs/comerciodigital/comercio.py b/source/app/controllers/pacs/comerciodigital/comercio.py
index b053c2e..e930278 100644
--- a/source/app/controllers/pacs/comerciodigital/comercio.py
+++ b/source/app/controllers/pacs/comerciodigital/comercio.py
@@ -42,6 +42,23 @@ logging.getLogger('requests').setLevel(logging.ERROR)
TIMEOUT = 10
+def pretty_print_POST(req):
+ """
+ At this point it is completely built and ready
+ to be fired; it is "prepared".
+
+ However pay attention at the formatting used in
+ this function because it is programmed to be pretty
+ printed and may differ from the actual request.
+ """
+ print('{}\n{}\r\n{}\r\n\r\n{}'.format(
+ '-----------START-----------',
+ req.method + ' ' + req.url,
+ '\r\n'.join('{}: {}'.format(k, v) for k, v in req.headers.items()),
+ req.body,
+ ))
+
+
class PACComercioDigital(object):
ws = 'https://{}.comercio-digital.mx/{}'
api = 'https://app2.comercio-digital.mx/{}'
@@ -91,6 +108,12 @@ class PACComercioDigital(object):
headers['host'] = url.split('/')[2]
headers['Content-type'] = 'text/plain'
headers['Connection'] = 'Keep-Alive'
+ headers['Expect'] = '100-continue'
+
+ if DEBUG:
+ req = requests.Request('POST', url, headers=headers, data=data)
+ prepared = req.prepare()
+ pretty_print_POST(prepared)
try:
result = requests.post(url, data=data, headers=headers, timeout=TIMEOUT)
@@ -150,7 +173,7 @@ class PACComercioDigital(object):
def _get_data_cancel(self, cfdi, info, auth):
info['tipo'] = 'cfdi'
info['key'] = base64.b64encode(info['key_enc']).decode()
- info['cer'] = base64.b64encode(info['cer']).decode()
+ info['cer'] = base64.b64encode(info['cer_ori']).decode()
NS_CFDI = {
'cfdi': 'http://www.sat.gob.mx/cfd/3',
diff --git a/source/app/controllers/utils.py b/source/app/controllers/utils.py
index 72312ef..8e02fe3 100644
--- a/source/app/controllers/utils.py
+++ b/source/app/controllers/utils.py
@@ -765,9 +765,9 @@ def get_pac_by_rfc(cfdi):
def _cancel_with_cert(invoice, args, auth, certificado):
cert = SATCertificate(certificado.cer, certificado.key_enc.encode())
pac = PACS[auth['pac']]()
- info = {'cer': cert.cer_pem, 'key': cert.key_pem, 'pass': '', 'args': args}
- # ~ info = {'cer': cert.cer_pem, 'key': cert.key_pem,
- # ~ 'key_enc': cert._key_der, 'pass': cert._p, 'args': args}
+ # ~ info = {'cer': cert.cer_pem, 'key': cert.key_pem, 'pass': '', 'args': args}
+ info = {'cer': cert.cer_pem, 'key': cert.key_pem, 'cer_ori': cert.cer,
+ 'key_enc': certificado.key, 'pass': decrypt(bytes(certificado.p12).decode(), certificado.serie), 'args': args}
result = pac.cancel(invoice.xml, info, auth)
if pac.error:
@@ -781,8 +781,8 @@ def _cancel_with_cert(invoice, args, auth, certificado):
def cancel_xml_sign(invoice, args, auth, certificado):
- if auth['pac'] == 'finkok':
- return _cancel_with_cert(invoice, args, auth, certificado)
+ # ~ if auth['pac'] == 'finkok':
+ return _cancel_with_cert(invoice, args, auth, certificado)
cert = SATCertificate(certificado.cer, certificado.key_enc.encode())
pac = PACS[auth['pac']]()
@@ -796,11 +796,11 @@ def cancel_xml_sign(invoice, args, auth, certificado):
'motivo': args['reason'],
'folio': folio_new,
}
+
template = TEMPLATE_CANCEL.format(**data)
tree = ET.fromstring(template.encode())
sign_xml = cert.sign_xml(tree)
# ~ print(sign_xml)
-
result = pac.cancel_xml(sign_xml, auth, invoice.xml)
if pac.error:
diff --git a/source/app/models/main.py b/source/app/models/main.py
index 1a4c8f4..3cf4462 100644
--- a/source/app/models/main.py
+++ b/source/app/models/main.py
@@ -1167,9 +1167,12 @@ class Certificado(BaseModel):
result['msg'] = 'El RFC del certificado no corresponde.'
return result
+ obj.key = cert._key
obj.key_enc = cert.key_enc
obj.cer = cert.cer
obj.serie = cert.serial_number
+ obj.cer_txt = cert.cer_txt
+ obj.p12 = utils.encrypt(cert._keyp, cert.serial_number)
obj.desde = cert.not_before
obj.hasta = cert.not_after
obj.save()
@@ -4158,6 +4161,12 @@ class Productos(BaseModel):
msg = 'Ya existe un producto con esta clave'
data = {'ok': False, 'row': {}, 'new': False, 'msg': msg}
return data
+ except Exception as e:
+ msg = str(e)
+ if 'productos_clave' in msg:
+ msg = 'Ya existe un producto con esta clave'
+ data = {'ok': False, 'row': {}, 'new': False, 'msg': msg}
+ return data
obj = Productos.get(Productos.id==id)
obj.impuestos = obj_taxes
@@ -5357,7 +5366,7 @@ class Facturas(BaseModel):
ubicaciones = values['ubicaciones']
for ubicacion in ubicaciones:
- if 'DistanciaRecorrida' in ubicacion:
+ if 'DistanciaRecorrida' in ubicacion and ubicacion['DistanciaRecorrida']:
total_distance += float(ubicacion['DistanciaRecorrida'])
if isinstance(ubicacion['DistanciaRecorrida'], (int, float)):
ubicacion['DistanciaRecorrida'] = f"{ubicacion['DistanciaRecorrida']:.2f}"
diff --git a/source/app/settings.py b/source/app/settings.py
index eecb97a..8765bc4 100644
--- a/source/app/settings.py
+++ b/source/app/settings.py
@@ -42,7 +42,7 @@ except ImportError:
DEBUG = DEBUG
-VERSION = '1.45.3'
+VERSION = '1.45.4'
EMAIL_SUPPORT = ('soporte@empresalibre.mx',)
TITLE_APP = '{} v{}'.format(TITLE_APP, VERSION)
@@ -256,38 +256,41 @@ DEFAULT_GLOBAL = {
'clave_sat': '01010101',
}
-TEMPLATE_CANCEL = """
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-"""
-# ~ TEMPLATE_CANCEL = """"""
+# ~ TEMPLATE_CANCEL = """
+# ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+ # ~
+# ~
+# ~ """
+
+TEMPLATE_CANCEL = """"""
diff --git a/source/static/js/controller/invoices.js b/source/static/js/controller/invoices.js
index 8b2802c..6fd607f 100644
--- a/source/static/js/controller/invoices.js
+++ b/source/static/js/controller/invoices.js
@@ -1097,22 +1097,6 @@ function search_product_by_key(key){
}
}
})
-
- //~ webix.ajax().get('/values/productokey', {'key': key}, {
- //~ error: function(text, data, xhr) {
- //~ msg_error('Error al consultar')
- //~ },
- //~ success: function(text, data, xhr){
- //~ var values = data.json()
- //~ if (values.ok){
- //~ set_product(values)
- //~ } else {
- //~ msg = 'No se encontró un producto con la clave: ' + key
- //~ msg_error(msg)
- //~ }
- //~ }
- //~ })
-
}
diff --git a/source/static/js/ui/invoices.js b/source/static/js/ui/invoices.js
index 6a05e3c..7014d5d 100644
--- a/source/static/js/ui/invoices.js
+++ b/source/static/js/ui/invoices.js
@@ -746,7 +746,7 @@ var opt_clave_entidad = [
{id: 'GRO', value: 'Guerrero'},
{id: 'HID', value: 'Hidalgo'},
{id: 'JAL', value: 'Jalisco'},
- {id: 'MEX', value: 'México'},
+ {id: 'MEX', value: 'Estado de México'},
{id: 'MIC', value: 'Michoacán'},
{id: 'MOR', value: 'Morelos'},
{id: 'NAC', value: 'Nacional'},
@@ -881,7 +881,7 @@ var opt_carta_estados = [
{id: 'GRO', value: 'Guerrero'},
{id: 'HID', value: 'Hidalgo'},
{id: 'JAL', value: 'Jalisco'},
- {id: 'MEX', value: 'México'},
+ {id: 'MEX', value: 'Estado de México'},
{id: 'MIC', value: 'Michoacán'},
{id: 'MOR', value: 'Morelos'},
{id: 'NAC', value: 'Nacional'},