Complemento divisas en PDF
This commit is contained in:
parent
8a4021ce49
commit
a0e1f83c25
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue