Complemento divisas en PDF

This commit is contained in:
Mauricio Baeza 2019-02-15 14:38:41 -06:00
parent 8a4021ce49
commit a0e1f83c25
4 changed files with 115 additions and 1 deletions

View File

@ -1084,6 +1084,12 @@ class LIBO(object):
self._set_cell('{ine.%s}' % k, v) self._set_cell('{ine.%s}' % k, v)
return return
def _divisas(self, data):
if data:
for k, v in data.items():
self._set_cell(f'{{divisas.{k}}}', v)
return
def _nomina(self, data): def _nomina(self, data):
if not data: if not data:
return return
@ -1310,6 +1316,8 @@ class LIBO(object):
self._donataria(data['donataria']) self._donataria(data['donataria'])
self._ine(data['ine']) self._ine(data['ine'])
self._divisas(data.get('divisas', {}))
self._cancelado(data['cancelada']) self._cancelado(data['cancelada'])
self._clean() self._clean()
return return

View File

@ -17,6 +17,7 @@
# ~ along with this program. If not, see <http://www.gnu.org/licenses/>. # ~ along with this program. If not, see <http://www.gnu.org/licenses/>.
import base64 import base64
import collections
import math import math
import smtplib import smtplib
@ -25,16 +26,88 @@ from email.mime.base import MIMEBase
from email.mime.text import MIMEText from email.mime.text import MIMEText
from email import encoders from email import encoders
from email.utils import formatdate from email.utils import formatdate
from io import BytesIO
import requests import requests
from cryptography.fernet import Fernet from cryptography.fernet import Fernet
from cryptography.hazmat.backends import default_backend from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import hashes
import lxml.etree as ET
TIMEOUT = 10 TIMEOUT = 10
#~ https://github.com/kennethreitz/requests/blob/v1.2.3/requests/structures.py#L37
class CaseInsensitiveDict(collections.MutableMapping):
"""
A case-insensitive ``dict``-like object.
Implements all methods and operations of
``collections.MutableMapping`` as well as dict's ``copy``. Also
provides ``lower_items``.
All keys are expected to be strings. The structure remembers the
case of the last key to be set, and ``iter(instance)``,
``keys()``, ``items()``, ``iterkeys()``, and ``iteritems()``
will contain case-sensitive keys. However, querying and contains
testing is case insensitive:
cid = CaseInsensitiveDict()
cid['Accept'] = 'application/json'
cid['aCCEPT'] == 'application/json' # True
list(cid) == ['Accept'] # True
For example, ``headers['content-encoding']`` will return the
value of a ``'Content-Encoding'`` response header, regardless
of how the header name was originally stored.
If the constructor, ``.update``, or equality comparison
operations are given keys that have equal ``.lower()``s, the
behavior is undefined.
"""
def __init__(self, data=None, **kwargs):
self._store = dict()
if data is None:
data = {}
self.update(data, **kwargs)
def __setitem__(self, key, value):
# Use the lowercased key for lookups, but store the actual
# key alongside the value.
self._store[key.lower()] = (key, value)
def __getitem__(self, key):
return self._store[key.lower()][1]
def __delitem__(self, key):
del self._store[key.lower()]
def __iter__(self):
return (casedkey for casedkey, mappedvalue in self._store.values())
def __len__(self):
return len(self._store)
def lower_items(self):
"""Like iteritems(), but with all lowercase keys."""
return (
(lowerkey, keyval[1])
for (lowerkey, keyval)
in self._store.items()
)
def __eq__(self, other):
if isinstance(other, collections.Mapping):
other = CaseInsensitiveDict(other)
else:
return NotImplemented
# Compare insensitively
return dict(self.lower_items()) == dict(other.lower_items())
# Copy is required
def copy(self):
return CaseInsensitiveDict(self._store.values())
def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, dict(self.items()))
class SendMail(object): class SendMail(object):
def __init__(self, config): def __init__(self, config):
@ -77,7 +150,6 @@ class SendMail(object):
if '535' in str(e): if '535' in str(e):
self._error = 'Nombre de usuario o contraseña inválidos' self._error = 'Nombre de usuario o contraseña inválidos'
return False return False
# ~ print (e)
if '534' in str(e) and 'gmail' in self._config['server']: if '534' in str(e) and 'gmail' in self._config['server']:
self._error = 'Necesitas activar el acceso a otras ' \ self._error = 'Necesitas activar el acceso a otras ' \
'aplicaciones en tu cuenta de GMail' 'aplicaciones en tu cuenta de GMail'
@ -128,6 +200,35 @@ class SendMail(object):
return return
class CfdiToDict(object):
NS = {
'cfdi': 'http://www.sat.gob.mx/cfd/3',
'divisas': 'http://www.sat.gob.mx/divisas',
}
def __init__(self, xml):
self._values = {}
self._root = ET.parse(BytesIO(xml.encode())).getroot()
self._get_values()
@property
def values(self):
return self._values
def _get_values(self):
self._complementos()
return
def _complementos(self):
complemento = self._root.xpath('//cfdi:Complemento', namespaces=self.NS)[0]
divisas = complemento.xpath('//divisas:Divisas', namespaces=self.NS)
if divisas:
d = CaseInsensitiveDict(divisas[0].attrib)
d.pop('version', '')
self._values.update({'divisas': d})
return
def send_mail(data): def send_mail(data):
msg = '' msg = ''
ok = True ok = True
@ -170,3 +271,4 @@ def to_bool(value):
def get_url(url): def get_url(url):
r = requests.get(url).text r = requests.get(url).text
return r return r

View File

@ -3816,7 +3816,11 @@ class Facturas(BaseModel):
pdf_from = Configuracion.get_('make_pdf_from') or '1' pdf_from = Configuracion.get_('make_pdf_from') or '1'
values = cls._get_not_in_xml(cls, obj, emisor) values = cls._get_not_in_xml(cls, obj, emisor)
#Tmp to v2
data = util.get_data_from_xml(obj, values) data = util.get_data_from_xml(obj, values)
data.update(utils.CfdiToDict(obj.xml).values)
doc = util.to_pdf(data, emisor.rfc, pdf_from=pdf_from) doc = util.to_pdf(data, emisor.rfc, pdf_from=pdf_from)
if sync: if sync: