2017-06-28 23:55:53 -05:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
2021-06-29 19:07:55 -05:00
|
|
|
import io
|
2018-01-01 02:14:30 -06:00
|
|
|
import os
|
2017-10-15 02:30:55 -05:00
|
|
|
import re
|
2017-10-16 00:02:51 -05:00
|
|
|
import smtplib
|
2017-12-26 20:30:22 -06:00
|
|
|
import ssl
|
2017-10-15 02:30:55 -05:00
|
|
|
import collections
|
2017-10-16 00:02:51 -05:00
|
|
|
|
2019-10-28 19:35:03 -06:00
|
|
|
from xml.sax.saxutils import escape
|
|
|
|
|
2017-10-15 02:30:55 -05:00
|
|
|
from collections import OrderedDict
|
2017-10-16 00:02:51 -05:00
|
|
|
from email.mime.multipart import MIMEMultipart
|
|
|
|
from email.mime.base import MIMEBase
|
|
|
|
from email.mime.text import MIMEText
|
|
|
|
from email import encoders
|
|
|
|
from email.utils import formatdate
|
2017-10-15 02:30:55 -05:00
|
|
|
|
2017-12-10 12:12:06 -06:00
|
|
|
import requests
|
2018-01-01 13:31:01 -06:00
|
|
|
try:
|
|
|
|
from escpos import printer
|
|
|
|
except ImportError:
|
|
|
|
printer = None
|
2017-12-10 12:12:06 -06:00
|
|
|
|
2017-10-24 00:03:07 -05:00
|
|
|
from reportlab.platypus import BaseDocTemplate, Frame, PageTemplate, Image
|
2017-10-23 00:45:41 -05:00
|
|
|
from reportlab.lib import colors
|
|
|
|
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
|
|
|
|
from reportlab.lib.units import cm
|
|
|
|
from reportlab.lib.pagesizes import letter
|
|
|
|
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
|
|
|
|
from reportlab.platypus import Paragraph, Table, TableStyle, Spacer
|
|
|
|
from reportlab.pdfgen import canvas
|
|
|
|
|
2017-10-15 02:30:55 -05:00
|
|
|
|
2019-01-15 00:45:08 -06:00
|
|
|
CANCEL = False
|
|
|
|
|
|
|
|
|
2017-10-23 00:45:41 -05:00
|
|
|
#~ https://github.com/kennethreitz/requests/blob/v1.2.3/requests/structures.py#L37
|
2017-10-15 02:30:55 -05:00
|
|
|
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 = OrderedDict()
|
|
|
|
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 str(dict(self.items()))
|
|
|
|
|
|
|
|
|
|
|
|
class NumLet(object):
|
|
|
|
|
|
|
|
def __init__(self, value, moneda, **args):
|
|
|
|
self._letras = self._letters(value, moneda)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def letras(self):
|
|
|
|
return self._letras.upper()
|
|
|
|
|
2018-01-12 11:02:13 -06:00
|
|
|
def _letters(self, numero, currency='MXN'):
|
|
|
|
# ~ print (currency)
|
|
|
|
monedas = {
|
|
|
|
'MXN': 'peso',
|
|
|
|
'USD': 'dólar',
|
|
|
|
'EUR': 'euro',
|
|
|
|
}
|
|
|
|
moneda = monedas.get(currency, currency)
|
|
|
|
tf = {
|
|
|
|
'MXN': 'm.n.',
|
|
|
|
}
|
2017-10-15 02:30:55 -05:00
|
|
|
|
2017-10-24 00:03:07 -05:00
|
|
|
texto_inicial = '-('
|
2018-01-12 11:02:13 -06:00
|
|
|
texto_final = '/100 {})-'.format(tf.get(currency, currency))
|
2017-10-15 02:30:55 -05:00
|
|
|
fraccion_letras = False
|
|
|
|
fraccion = ''
|
|
|
|
|
|
|
|
enletras = texto_inicial
|
|
|
|
numero = abs(numero)
|
|
|
|
numtmp = '%015d' % numero
|
|
|
|
|
|
|
|
if numero < 1:
|
|
|
|
enletras += 'cero ' + self._plural(moneda) + ' '
|
|
|
|
else:
|
|
|
|
enletras += self._numlet(numero)
|
|
|
|
if numero == 1 or numero < 2:
|
|
|
|
enletras += moneda + ' '
|
|
|
|
elif int(''.join(numtmp[3:])) == 0 or int(''.join(numtmp[9:])) == 0:
|
|
|
|
enletras += 'de ' + self._plural(moneda) + ' '
|
|
|
|
else:
|
|
|
|
enletras += self._plural(moneda) + ' '
|
|
|
|
|
|
|
|
decimal = '%0.2f' % numero
|
|
|
|
decimal = decimal.split('.')[1]
|
|
|
|
#~ decimal = int((numero-int(numero))*100)
|
|
|
|
if fraccion_letras:
|
|
|
|
if decimal == 0:
|
|
|
|
enletras += 'con cero ' + self._plural(fraccion)
|
|
|
|
elif decimal == 1:
|
|
|
|
enletras += 'con un ' + fraccion
|
|
|
|
else:
|
|
|
|
enletras += 'con ' + self._numlet(int(decimal)) + self.plural(fraccion)
|
|
|
|
else:
|
|
|
|
enletras += decimal
|
|
|
|
|
|
|
|
enletras += texto_final
|
|
|
|
return enletras
|
|
|
|
|
|
|
|
def _numlet(self, numero):
|
|
|
|
numtmp = '%015d' % numero
|
|
|
|
co1=0
|
|
|
|
letras = ''
|
|
|
|
leyenda = ''
|
|
|
|
for co1 in range(0,5):
|
|
|
|
inicio = co1*3
|
|
|
|
cen = int(numtmp[inicio:inicio+1][0])
|
|
|
|
dec = int(numtmp[inicio+1:inicio+2][0])
|
|
|
|
uni = int(numtmp[inicio+2:inicio+3][0])
|
|
|
|
letra3 = self.centena(uni, dec, cen)
|
|
|
|
letra2 = self.decena(uni, dec)
|
|
|
|
letra1 = self.unidad(uni, dec)
|
|
|
|
|
|
|
|
if co1 == 0:
|
|
|
|
if (cen+dec+uni) == 1:
|
|
|
|
leyenda = 'billon '
|
|
|
|
elif (cen+dec+uni) > 1:
|
|
|
|
leyenda = 'billones '
|
|
|
|
elif co1 == 1:
|
|
|
|
if (cen+dec+uni) >= 1 and int(''.join(numtmp[6:9])) == 0:
|
|
|
|
leyenda = "mil millones "
|
|
|
|
elif (cen+dec+uni) >= 1:
|
|
|
|
leyenda = "mil "
|
|
|
|
elif co1 == 2:
|
|
|
|
if (cen+dec) == 0 and uni == 1:
|
|
|
|
leyenda = 'millon '
|
|
|
|
elif cen > 0 or dec > 0 or uni > 1:
|
|
|
|
leyenda = 'millones '
|
|
|
|
elif co1 == 3:
|
|
|
|
if (cen+dec+uni) >= 1:
|
|
|
|
leyenda = 'mil '
|
|
|
|
elif co1 == 4:
|
|
|
|
if (cen+dec+uni) >= 1:
|
|
|
|
leyenda = ''
|
|
|
|
|
|
|
|
letras += letra3 + letra2 + letra1 + leyenda
|
|
|
|
letra1 = ''
|
|
|
|
letra2 = ''
|
|
|
|
letra3 = ''
|
|
|
|
leyenda = ''
|
|
|
|
return letras
|
|
|
|
|
|
|
|
def centena(self, uni, dec, cen):
|
|
|
|
letras = ''
|
|
|
|
numeros = ["","","doscientos ","trescientos ","cuatrocientos ","quinientos ","seiscientos ","setecientos ","ochocientos ","novecientos "]
|
|
|
|
if cen == 1:
|
|
|
|
if (dec+uni) == 0:
|
|
|
|
letras = 'cien '
|
|
|
|
else:
|
|
|
|
letras = 'ciento '
|
|
|
|
elif cen >= 2 and cen <= 9:
|
|
|
|
letras = numeros[cen]
|
|
|
|
return letras
|
|
|
|
|
|
|
|
def decena(self, uni, dec):
|
|
|
|
letras = ''
|
|
|
|
numeros = ["diez ","once ","doce ","trece ","catorce ","quince ","dieci","dieci","dieci","dieci"]
|
|
|
|
decenas = ["","","","treinta ","cuarenta ","cincuenta ","sesenta ","setenta ","ochenta ","noventa "]
|
|
|
|
if dec == 1:
|
|
|
|
letras = numeros[uni]
|
|
|
|
elif dec == 2:
|
|
|
|
if uni == 0:
|
|
|
|
letras = 'veinte '
|
|
|
|
elif uni > 0:
|
|
|
|
letras = 'veinti'
|
|
|
|
elif dec >= 3 and dec <= 9:
|
|
|
|
letras = decenas[dec]
|
|
|
|
if uni > 0 and dec > 2:
|
|
|
|
letras = letras+'y '
|
|
|
|
return letras
|
|
|
|
|
|
|
|
def unidad(self, uni, dec):
|
|
|
|
letras = ''
|
|
|
|
numeros = ["","un ","dos ","tres ","cuatro ","cinco ","seis ","siete ","ocho ","nueve "]
|
|
|
|
if dec != 1:
|
|
|
|
if uni > 0 and uni <= 5:
|
|
|
|
letras = numeros[uni]
|
|
|
|
if uni >= 6 and uni <= 9:
|
|
|
|
letras = numeros[uni]
|
|
|
|
return letras
|
|
|
|
|
|
|
|
def _plural(self, palabra):
|
|
|
|
if re.search('[aeiou]$', palabra):
|
|
|
|
return re.sub('$', 's', palabra)
|
|
|
|
else:
|
|
|
|
return palabra + 'es'
|
2017-10-16 00:02:51 -05:00
|
|
|
|
|
|
|
|
|
|
|
class SendMail(object):
|
|
|
|
|
|
|
|
def __init__(self, config):
|
|
|
|
self._config = config
|
|
|
|
self._server = None
|
|
|
|
self._error = ''
|
|
|
|
self._is_connect = self._login()
|
|
|
|
|
|
|
|
@property
|
|
|
|
def is_connect(self):
|
|
|
|
return self._is_connect
|
|
|
|
|
|
|
|
@property
|
|
|
|
def error(self):
|
|
|
|
return self._error
|
|
|
|
|
|
|
|
def _login(self):
|
|
|
|
try:
|
2018-01-16 10:12:53 -06:00
|
|
|
if self._config['ssl'] and (
|
|
|
|
'gmail' in self._config['servidor'] or
|
|
|
|
'outlook' in self._config['servidor']):
|
2017-12-26 20:30:22 -06:00
|
|
|
self._server = smtplib.SMTP(
|
2017-10-16 00:02:51 -05:00
|
|
|
self._config['servidor'],
|
|
|
|
self._config['puerto'], timeout=10)
|
2017-12-26 20:30:22 -06:00
|
|
|
self._server.ehlo()
|
|
|
|
self._server.starttls()
|
|
|
|
self._server.ehlo()
|
2017-12-29 23:01:04 -06:00
|
|
|
elif self._config['ssl']:
|
|
|
|
self._server = smtplib.SMTP_SSL(
|
|
|
|
self._config['servidor'],
|
|
|
|
self._config['puerto'], timeout=10)
|
|
|
|
self._server.ehlo()
|
2017-10-16 00:02:51 -05:00
|
|
|
else:
|
|
|
|
self._server = smtplib.SMTP(
|
|
|
|
self._config['servidor'],
|
|
|
|
self._config['puerto'], timeout=10)
|
|
|
|
self._server.login(self._config['usuario'], self._config['contra'])
|
|
|
|
return True
|
|
|
|
except smtplib.SMTPAuthenticationError as e:
|
|
|
|
if '535' in str(e):
|
|
|
|
self._error = 'Nombre de usuario o contraseña inválidos'
|
|
|
|
return False
|
2018-10-27 01:15:20 -05:00
|
|
|
# ~ print (e)
|
2017-10-16 00:02:51 -05:00
|
|
|
if '534' in str(e) and 'gmail' in self._config['servidor']:
|
|
|
|
self._error = 'Necesitas activar el acceso a otras ' \
|
|
|
|
'aplicaciones en tu cuenta de GMail'
|
|
|
|
return False
|
|
|
|
except smtplib.SMTPException as e:
|
|
|
|
self._error = str(e)
|
|
|
|
return False
|
|
|
|
except Exception as e:
|
|
|
|
self._error = str(e)
|
|
|
|
return False
|
|
|
|
return
|
|
|
|
|
|
|
|
def send(self, options):
|
|
|
|
try:
|
|
|
|
message = MIMEMultipart()
|
|
|
|
message['From'] = self._config['usuario']
|
|
|
|
message['To'] = options['para']
|
|
|
|
message['CC'] = options['copia']
|
|
|
|
message['Subject'] = options['asunto']
|
|
|
|
message['Date'] = formatdate(localtime=True)
|
2017-12-29 23:33:47 -06:00
|
|
|
if options['confirmar']:
|
|
|
|
message['Disposition-Notification-To'] = message['From']
|
2017-10-16 00:02:51 -05:00
|
|
|
message.attach(MIMEText(options['mensaje'], 'html'))
|
|
|
|
for f in options['files']:
|
|
|
|
part = MIMEBase('application', 'octet-stream')
|
2018-02-09 15:20:06 -06:00
|
|
|
if isinstance(f[0], str):
|
|
|
|
part.set_payload(f[0].encode('utf-8'))
|
|
|
|
else:
|
|
|
|
part.set_payload(f[0])
|
2017-10-16 00:02:51 -05:00
|
|
|
encoders.encode_base64(part)
|
|
|
|
part.add_header(
|
|
|
|
'Content-Disposition',
|
|
|
|
"attachment; filename={}".format(f[1]))
|
|
|
|
message.attach(part)
|
|
|
|
|
|
|
|
receivers = options['para'].split(',') + options['copia'].split(',')
|
|
|
|
self._server.sendmail(
|
|
|
|
self._config['usuario'], receivers, message.as_string())
|
|
|
|
return ''
|
|
|
|
except Exception as e:
|
|
|
|
return str(e)
|
|
|
|
|
|
|
|
def close(self):
|
|
|
|
try:
|
|
|
|
self._server.quit()
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
return
|
2017-10-23 00:45:41 -05:00
|
|
|
|
|
|
|
|
|
|
|
class NumberedCanvas(canvas.Canvas):
|
|
|
|
X = 20.59 * cm
|
2019-01-14 02:07:42 -06:00
|
|
|
XC = 21.6 * cm / 2 + 1.5 * cm
|
2017-10-24 00:03:07 -05:00
|
|
|
Y = 1.5 * cm
|
2017-10-23 00:45:41 -05:00
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
canvas.Canvas.__init__(self, *args, **kwargs)
|
|
|
|
self._saved_page_states = []
|
|
|
|
|
|
|
|
def showPage(self):
|
|
|
|
self._saved_page_states.append(dict(self.__dict__))
|
|
|
|
self._startPage()
|
|
|
|
return
|
|
|
|
|
|
|
|
def save(self):
|
|
|
|
"""add page info to each page (page x of y)"""
|
|
|
|
page_count = len(self._saved_page_states)
|
|
|
|
for state in self._saved_page_states:
|
|
|
|
self.__dict__.update(state)
|
|
|
|
self.draw_page_number(page_count)
|
2019-01-15 00:45:08 -06:00
|
|
|
self.draw_cancel()
|
2017-10-23 00:45:41 -05:00
|
|
|
canvas.Canvas.showPage(self)
|
|
|
|
canvas.Canvas.save(self)
|
|
|
|
return
|
|
|
|
|
|
|
|
def draw_page_number(self, page_count):
|
|
|
|
self.setFont('Helvetica', 8)
|
2019-01-14 02:07:42 -06:00
|
|
|
# ~ self.setFillColor(colors.darkred)
|
|
|
|
text = f'Página {self._pageNumber} de {page_count}'
|
2017-10-23 00:45:41 -05:00
|
|
|
self.drawRightString(self.X, self.Y, text)
|
2019-01-14 02:07:42 -06:00
|
|
|
|
|
|
|
text = 'Este documento es una representación impresa de un CFDI'
|
|
|
|
self.drawCentredString(self.XC, self.Y, text)
|
|
|
|
|
|
|
|
text = 'Factura elaborada con software libre'
|
2017-10-24 00:03:07 -05:00
|
|
|
self.drawString(1.5 * cm, 1.5 * cm, text)
|
2017-10-23 00:45:41 -05:00
|
|
|
return
|
|
|
|
|
2019-01-15 00:45:08 -06:00
|
|
|
def draw_cancel(self):
|
|
|
|
global CANCEL
|
|
|
|
if CANCEL:
|
|
|
|
self.rotate(45)
|
|
|
|
self.setFont('Helvetica', 100)
|
|
|
|
self.setFillColor(colors.red)
|
|
|
|
text = 'Cancelada'
|
|
|
|
self.drawCentredString(20 * cm, 3 * cm, text)
|
|
|
|
return
|
2017-10-23 00:45:41 -05:00
|
|
|
|
|
|
|
class TemplateInvoice(BaseDocTemplate):
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
# letter 21.6 x 27.9
|
|
|
|
kwargs['pagesize'] = letter
|
|
|
|
kwargs['rightMargin'] = 1 * cm
|
|
|
|
kwargs['leftMargin'] = 1 * cm
|
|
|
|
kwargs['topMargin'] = 1.5 * cm
|
|
|
|
kwargs['bottomMargin'] = 1.5 * cm
|
|
|
|
BaseDocTemplate.__init__(self, *args, **kwargs)
|
|
|
|
self._data = {}
|
|
|
|
self._rows = []
|
|
|
|
|
|
|
|
def _set_rect(self, style):
|
|
|
|
color = style.pop('color', 'black')
|
|
|
|
if isinstance(color, str):
|
|
|
|
self.canv.setFillColor(getattr(colors, color))
|
|
|
|
else:
|
|
|
|
self.canv.setFillColorRGB(*color)
|
|
|
|
|
|
|
|
keys = ('x', 'y', 'width', 'height', 'radius')
|
|
|
|
for k in keys:
|
|
|
|
style[k] = style[k] * cm
|
|
|
|
|
|
|
|
self.canv.roundRect(**style)
|
|
|
|
return
|
|
|
|
|
|
|
|
def _set_text(self, styles, value):
|
2017-10-24 00:03:07 -05:00
|
|
|
text = styles.pop('valor', '')
|
|
|
|
if not value:
|
|
|
|
value = text
|
|
|
|
|
2017-10-23 00:45:41 -05:00
|
|
|
rect = styles['rectangulo']
|
|
|
|
if value:
|
|
|
|
self.canv.setFillColor(colors.white)
|
|
|
|
self.canv.setStrokeColor(colors.white)
|
|
|
|
ps = ParagraphStyle(**styles['estilo'])
|
|
|
|
p = Paragraph(value, ps)
|
|
|
|
p.wrap(rect['width'] * cm, rect['height'] * cm)
|
|
|
|
p.drawOn(self.canv, rect['x'] * cm, rect['y'] * cm)
|
|
|
|
|
|
|
|
self._set_rect(rect)
|
|
|
|
return
|
|
|
|
|
|
|
|
def _emisor(self, styles, data):
|
2017-10-24 00:03:07 -05:00
|
|
|
logo_path = data.pop('logo', '')
|
|
|
|
logo_style = styles.pop('logo', {})
|
2019-01-14 02:07:42 -06:00
|
|
|
logo_path2 = data.pop('logo2', '')
|
|
|
|
logo_style2 = styles.pop('logo2', {})
|
2019-01-15 00:45:08 -06:00
|
|
|
sucursales = styles.pop('sucursales', {})
|
2017-10-24 00:03:07 -05:00
|
|
|
|
|
|
|
for k, v in styles.items():
|
|
|
|
self._set_text(styles[k], data.get(k, ''))
|
|
|
|
|
|
|
|
if logo_path and logo_style:
|
|
|
|
rect = logo_style['rectangulo']
|
|
|
|
keys = ('x', 'y', 'width', 'height')
|
|
|
|
for k in keys:
|
|
|
|
rect[k] = rect[k] * cm
|
|
|
|
self.canv.drawImage(logo_path, **rect)
|
2019-01-14 02:07:42 -06:00
|
|
|
|
|
|
|
if logo_path2 and logo_style2:
|
|
|
|
rect = logo_style2['rectangulo']
|
|
|
|
keys = ('x', 'y', 'width', 'height')
|
|
|
|
for k in keys:
|
|
|
|
rect[k] = rect[k] * cm
|
|
|
|
self.canv.drawImage(logo_path2, **rect)
|
|
|
|
|
2019-01-15 00:45:08 -06:00
|
|
|
if sucursales:
|
|
|
|
for k, sucursal in sucursales.items():
|
|
|
|
values = sucursal.pop('textos')
|
|
|
|
x = sucursal['rectangulo']['x']
|
|
|
|
y = sucursal['rectangulo']['y']
|
|
|
|
w = sucursal['rectangulo']['width']
|
|
|
|
h = sucursal['rectangulo']['height']
|
|
|
|
for v in values:
|
|
|
|
self._set_text(sucursal, v)
|
|
|
|
y -= h
|
|
|
|
sucursal['rectangulo']['x'] = x
|
|
|
|
sucursal['rectangulo']['y'] = y
|
|
|
|
sucursal['rectangulo']['width'] = w
|
|
|
|
sucursal['rectangulo']['height'] = h
|
2017-10-24 00:03:07 -05:00
|
|
|
return
|
|
|
|
|
|
|
|
def _receptor(self, styles, data):
|
|
|
|
title = styles.pop('titulo', {})
|
|
|
|
|
2017-10-23 00:45:41 -05:00
|
|
|
for k, v in styles.items():
|
|
|
|
self._set_text(styles[k], data.get(k, ''))
|
2017-10-24 00:03:07 -05:00
|
|
|
|
|
|
|
if title:
|
|
|
|
rect = title['rectangulo']
|
|
|
|
self.canv.saveState()
|
|
|
|
self.canv.rotate(90)
|
|
|
|
value = title.pop('valor', '')
|
|
|
|
title['rectangulo']['x'], title['rectangulo']['y'] = \
|
|
|
|
title['rectangulo']['y'], title['rectangulo']['x'] * -1
|
|
|
|
self._set_text(title, value)
|
|
|
|
self.canv.restoreState()
|
|
|
|
return
|
|
|
|
|
|
|
|
def _comprobante1(self, styles, data):
|
|
|
|
title = styles.pop('titulo', {})
|
2019-01-14 02:07:42 -06:00
|
|
|
self.canv.setTitle(f"Factura {data.get('seriefolio', '')}")
|
2017-10-24 00:03:07 -05:00
|
|
|
|
|
|
|
for k, v in styles.items():
|
|
|
|
self._set_text(styles[k], data.get(k, ''))
|
|
|
|
|
|
|
|
if title:
|
|
|
|
rect = title['rectangulo']
|
|
|
|
self.canv.saveState()
|
|
|
|
self.canv.rotate(90)
|
|
|
|
value = title.pop('valor', '')
|
|
|
|
title['rectangulo']['x'], title['rectangulo']['y'] = \
|
|
|
|
title['rectangulo']['y'], title['rectangulo']['x'] * -1
|
|
|
|
self._set_text(title, value)
|
|
|
|
self.canv.restoreState()
|
2017-10-23 00:45:41 -05:00
|
|
|
return
|
|
|
|
|
|
|
|
def afterPage(self):
|
|
|
|
encabezado = self._custom_styles['encabezado']
|
|
|
|
self.canv.saveState()
|
|
|
|
self._emisor(encabezado['emisor'], self._data['emisor'])
|
2017-10-24 00:03:07 -05:00
|
|
|
self._receptor(encabezado['receptor'], self._data['receptor'])
|
|
|
|
self._comprobante1(encabezado['comprobante'], self._data['comprobante'])
|
2017-10-23 00:45:41 -05:00
|
|
|
self.canv.restoreState()
|
|
|
|
return
|
|
|
|
|
2017-10-24 00:03:07 -05:00
|
|
|
def _currency(self, value, simbol='$'):
|
|
|
|
return '{} {:,.2f}'.format(simbol, float(value))
|
|
|
|
|
|
|
|
def _format(self, value, field):
|
|
|
|
fields = ('valorunitario', 'importe')
|
|
|
|
if field in fields:
|
|
|
|
return self._currency(value)
|
2019-01-14 02:07:42 -06:00
|
|
|
|
|
|
|
if field == 'descripcion':
|
|
|
|
html = '<font color="black" size=7>{}</font>'
|
|
|
|
style_bt = getSampleStyleSheet()['BodyText']
|
|
|
|
return Paragraph(html.format(value), style_bt)
|
|
|
|
|
2017-10-24 00:03:07 -05:00
|
|
|
return value
|
|
|
|
|
|
|
|
def _conceptos(self, conceptos):
|
|
|
|
headers = (('Clave', 'Descripción', 'Unidad', 'Cantidad',
|
|
|
|
'Valor Unitario', 'Importe'),)
|
|
|
|
fields = ('noidentificacion', 'descripcion', 'unidad', 'cantidad',
|
|
|
|
'valorunitario', 'importe')
|
|
|
|
rows = []
|
|
|
|
for concepto in conceptos:
|
|
|
|
row = tuple([self._format(concepto[f], f) for f in fields])
|
|
|
|
rows.append(row)
|
|
|
|
return headers + tuple(rows)
|
|
|
|
|
|
|
|
def _totales(self, values):
|
|
|
|
#~ print (values)
|
|
|
|
rows = [('', 'Subtotal', self._currency(values['subtotal']))]
|
|
|
|
|
|
|
|
for tax in values['traslados']:
|
|
|
|
row = ('', tax[0], self._currency(tax[1]))
|
|
|
|
rows.append(row)
|
|
|
|
for tax in values['retenciones']:
|
|
|
|
row = ('', tax[0], self._currency(tax[1]))
|
|
|
|
rows.append(row)
|
|
|
|
for tax in values['taxlocales']:
|
|
|
|
row = ('', tax[0], self._currency(tax[1]))
|
|
|
|
rows.append(row)
|
|
|
|
|
|
|
|
row = ('', 'Total', self._currency(values['total']))
|
|
|
|
rows.append(row)
|
|
|
|
|
|
|
|
widths = [12.5 * cm, 4 * cm, 3 * cm]
|
|
|
|
table_styles = [
|
|
|
|
('GRID', (0, 0), (-1, -1), 0.05 * cm, colors.white),
|
|
|
|
('ALIGN', (1, 0), (-1, -1), 'RIGHT'),
|
|
|
|
('FONTSIZE', (0, 0), (-1, -1), 8),
|
2019-01-14 02:07:42 -06:00
|
|
|
('BACKGROUND', (1, 0), (-1, -1), colors.lightgrey),
|
|
|
|
('TEXTCOLOR', (1, 0), (-1, -1), colors.black),
|
2017-10-24 00:03:07 -05:00
|
|
|
('FACE', (1, 0), (-1, -1), 'Helvetica-Bold'),
|
|
|
|
]
|
|
|
|
table = Table(rows, colWidths=widths, spaceBefore=0.25*cm)
|
|
|
|
table.setStyle(TableStyle(table_styles))
|
|
|
|
return table
|
|
|
|
|
|
|
|
def _comprobante2(self, styles, data):
|
2019-01-14 02:07:42 -06:00
|
|
|
leyendas = styles.pop('leyendas', {})
|
2017-10-24 00:03:07 -05:00
|
|
|
|
|
|
|
ls = []
|
|
|
|
for k, v in styles.items():
|
|
|
|
if k in data:
|
|
|
|
if 'spaceBefore' in v['estilo']:
|
|
|
|
v['estilo']['spaceBefore'] = v['estilo']['spaceBefore'] * cm
|
|
|
|
ps = ParagraphStyle(**v['estilo'])
|
2019-10-28 19:35:03 -06:00
|
|
|
p = Paragraph(escape(data[k]), ps)
|
2017-10-24 00:03:07 -05:00
|
|
|
ls.append(p)
|
2019-01-15 23:16:34 -06:00
|
|
|
elif k=='formametodopago':
|
|
|
|
ps = ParagraphStyle(**v['estilo'])
|
|
|
|
v = f"{data['formadepago']} - {data['metododepago']}"
|
|
|
|
p = Paragraph(v, ps)
|
|
|
|
ls.append(p)
|
|
|
|
elif k=='monedatipocambio':
|
|
|
|
ps = ParagraphStyle(**v['estilo'])
|
|
|
|
v = f"{data['moneda']} - {data['tipocambio']}"
|
|
|
|
p = Paragraph(v, ps)
|
|
|
|
ls.append(p)
|
2017-10-24 00:03:07 -05:00
|
|
|
|
2021-06-29 19:07:55 -05:00
|
|
|
cbb = Image(data['cbb'])
|
2017-10-24 00:03:07 -05:00
|
|
|
cbb.drawHeight = 4 * cm
|
|
|
|
cbb.drawWidth = 4 * cm
|
|
|
|
|
|
|
|
style_bt = getSampleStyleSheet()['BodyText']
|
|
|
|
style_bt.leading = 8
|
|
|
|
html_t = '<b><font size=6>{}</font></b>'
|
2019-01-14 02:07:42 -06:00
|
|
|
html = '<font color="black" size=5>{}</font>'
|
2017-10-24 00:03:07 -05:00
|
|
|
msg = 'Cadena original del complemento de certificación digital del SAT'
|
|
|
|
rows = [
|
|
|
|
(cbb, Paragraph(html_t.format('Sello Digital del CFDI'), style_bt)),
|
|
|
|
('', Paragraph(html.format(data['sellocfd']), style_bt)),
|
|
|
|
('', Paragraph(html_t.format('Sello Digital del SAT'), style_bt)),
|
|
|
|
('', Paragraph(html.format(data['sellosat']), style_bt)),
|
|
|
|
('', Paragraph(html_t.format(msg), style_bt)),
|
|
|
|
('', Paragraph(html.format(data['cadenaoriginal']), style_bt)),
|
|
|
|
]
|
|
|
|
|
|
|
|
widths = [4 * cm, 15.5 * cm]
|
|
|
|
table_styles = [
|
|
|
|
('FONTSIZE', (0, 0), (-1, -1), 6),
|
|
|
|
('SPAN', (0, 0), (0, -1)),
|
|
|
|
('FACE', (1, 0), (1, 0), 'Helvetica-Bold'),
|
2019-01-14 02:07:42 -06:00
|
|
|
('BACKGROUND', (1, 1), (1, 1), colors.lightgrey),
|
2017-10-24 00:03:07 -05:00
|
|
|
('TEXTCOLOR', (1, 1), (1, 1), colors.darkred),
|
|
|
|
('FACE', (1, 2), (1, 2), 'Helvetica-Bold'),
|
2019-01-14 02:07:42 -06:00
|
|
|
('BACKGROUND', (1, 3), (1, 3), colors.lightgrey),
|
2017-10-24 00:03:07 -05:00
|
|
|
('TEXTCOLOR', (1, 3), (1, 3), colors.darkred),
|
|
|
|
('FACE', (1, 4), (1, 4), 'Helvetica-Bold'),
|
2019-01-14 02:07:42 -06:00
|
|
|
('BACKGROUND', (1, 5), (1, 5), colors.lightgrey),
|
2017-10-24 00:03:07 -05:00
|
|
|
('TEXTCOLOR', (1, 5), (1, 5), colors.darkred),
|
|
|
|
('ALIGN', (0, 0), (0, 0), 'CENTER'),
|
|
|
|
('VALIGN', (0, 0), (0, 0), 'MIDDLE'),
|
|
|
|
]
|
|
|
|
table = Table(rows, colWidths=widths)
|
|
|
|
table.setStyle(TableStyle(table_styles))
|
|
|
|
ls.append(table)
|
|
|
|
|
2019-01-14 02:07:42 -06:00
|
|
|
if leyendas:
|
|
|
|
if 'spaceBefore' in leyendas['estilo']:
|
|
|
|
leyendas['estilo']['spaceBefore'] = \
|
|
|
|
leyendas['estilo']['spaceBefore'] * cm
|
|
|
|
for t in leyendas['textos']:
|
|
|
|
ps = ParagraphStyle(**leyendas['estilo'])
|
|
|
|
p = Paragraph(t, ps)
|
|
|
|
ls.append(p)
|
2017-10-24 00:03:07 -05:00
|
|
|
|
|
|
|
return ls
|
|
|
|
|
2017-10-23 00:45:41 -05:00
|
|
|
@property
|
|
|
|
def custom_styles(self):
|
|
|
|
return self._custom_styles
|
|
|
|
@custom_styles.setter
|
|
|
|
def custom_styles(self, values):
|
|
|
|
self._custom_styles = values
|
|
|
|
|
|
|
|
@property
|
|
|
|
def data(self):
|
|
|
|
return self._data
|
|
|
|
@data.setter
|
|
|
|
def data(self, values):
|
2019-01-15 00:45:08 -06:00
|
|
|
global CANCEL
|
|
|
|
# ~ print (values)
|
2017-10-23 00:45:41 -05:00
|
|
|
self._data = values
|
2019-01-15 00:45:08 -06:00
|
|
|
CANCEL = self._data['cancelada']
|
2017-10-24 00:03:07 -05:00
|
|
|
|
|
|
|
rows = self._conceptos(self._data['conceptos'])
|
|
|
|
widths = [2 * cm, 9 * cm, 1.5 * cm, 2 * cm, 2 * cm, 3 * cm]
|
|
|
|
table_styles = [
|
|
|
|
('GRID', (0, 0), (-1, -1), 0.05 * cm, colors.white),
|
|
|
|
('FONTSIZE', (0, 0), (-1, 0), 7),
|
|
|
|
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
|
|
|
|
('TEXTCOLOR', (0, 0), (-1, 0), colors.white),
|
|
|
|
('FACE', (0, 0), (-1, 0), 'Helvetica-Bold'),
|
2019-01-14 02:07:42 -06:00
|
|
|
('BACKGROUND', (0, 0), (-1, 0), colors.grey),
|
2017-10-24 00:03:07 -05:00
|
|
|
('FONTSIZE', (0, 1), (-1, -1), 7),
|
|
|
|
('VALIGN', (0, 1), (-1, -1), 'TOP'),
|
|
|
|
('ALIGN', (0, 1), (0, -1), 'CENTER'),
|
|
|
|
('ALIGN', (2, 1), (2, -1), 'CENTER'),
|
|
|
|
('ALIGN', (3, 1), (5, -1), 'RIGHT'),
|
2019-01-14 02:07:42 -06:00
|
|
|
('LINEBELOW', (0, 1), (-1, -1), 0.05 * cm, colors.grey),
|
2017-10-24 00:03:07 -05:00
|
|
|
('LINEBEFORE', (0, 1), (-1, -1), 0.05 * cm, colors.white),
|
|
|
|
]
|
|
|
|
table_conceptos = Table(rows, colWidths=widths, repeatRows=1)
|
|
|
|
table_conceptos.setStyle(TableStyle(table_styles))
|
|
|
|
|
|
|
|
totales = self._totales(self.data['totales'])
|
|
|
|
comprobante = self._comprobante2(
|
|
|
|
self._custom_styles['comprobante'], self.data['comprobante'])
|
|
|
|
|
2019-01-15 00:45:08 -06:00
|
|
|
self._rows = [Spacer(0, 5.7 * cm), table_conceptos, totales] + comprobante
|
2017-10-23 00:45:41 -05:00
|
|
|
|
|
|
|
def render(self):
|
|
|
|
frame = Frame(self.leftMargin, self.bottomMargin,
|
|
|
|
self.width, self.height, id='normal')
|
|
|
|
template = PageTemplate(id='invoice', frames=frame)
|
|
|
|
self.addPageTemplates([template])
|
|
|
|
self.build(self._rows, canvasmaker=NumberedCanvas)
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
|
|
class ReportTemplate(BaseDocTemplate):
|
|
|
|
"""Override the BaseDocTemplate class to do custom handle_XXX actions"""
|
|
|
|
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
# letter 21.6 x 27.9
|
|
|
|
kwargs['pagesize'] = letter
|
|
|
|
kwargs['rightMargin'] = 1 * cm
|
|
|
|
kwargs['leftMargin'] = 1 * cm
|
|
|
|
kwargs['topMargin'] = 1.5 * cm
|
|
|
|
kwargs['bottomMargin'] = 1.5 * cm
|
|
|
|
BaseDocTemplate.__init__(self, *args, **kwargs)
|
|
|
|
self.styles = getSampleStyleSheet()
|
|
|
|
self.header = {}
|
|
|
|
self.data = []
|
|
|
|
|
|
|
|
def afterPage(self):
|
|
|
|
"""Called after each page has been processed"""
|
|
|
|
self.canv.saveState()
|
|
|
|
date = datetime.datetime.today().strftime('%A, %d de %B del %Y')
|
|
|
|
self.canv.setStrokeColorRGB(0.5, 0, 0)
|
|
|
|
self.canv.setFont("Helvetica", 8)
|
|
|
|
self.canv.drawRightString(20.59 * cm, 26.9 * cm, date)
|
|
|
|
self.canv.line(1 * cm, 26.4 * cm, 20.6 * cm, 26.4 * cm)
|
|
|
|
|
|
|
|
path_cur = os.path.dirname(os.path.realpath(__file__))
|
|
|
|
path_img = os.path.join(path_cur, 'logo.png')
|
|
|
|
try:
|
|
|
|
self.canv.drawImage(path_img, 1 * cm, 24.2 * cm, 4 * cm, 2 * cm)
|
|
|
|
except:
|
|
|
|
pass
|
|
|
|
|
|
|
|
self.canv.roundRect(
|
|
|
|
5 * cm, 25.4 * cm, 15.5 * cm, 0.6 * cm, 0.15 * cm,
|
|
|
|
stroke=True, fill=False)
|
|
|
|
self.canv.setFont('Helvetica-BoldOblique', 10)
|
|
|
|
self.canv.drawCentredString(12.75 * cm, 25.6 * cm, self.header['emisor'])
|
|
|
|
|
|
|
|
self.canv.roundRect(
|
|
|
|
5 * cm, 24.4 * cm, 15.5 * cm, 0.6 * cm, 0.15 * cm,
|
|
|
|
stroke=True, fill=False)
|
|
|
|
self.canv.setFont('Helvetica-BoldOblique', 9)
|
|
|
|
self.canv.drawCentredString(12.75 * cm, 24.6 * cm, self.header['title'])
|
|
|
|
|
|
|
|
self.canv.line(1 * cm, 1.5 * cm, 20.6 * cm, 1.5 * cm)
|
|
|
|
self.canv.restoreState()
|
|
|
|
return
|
|
|
|
|
|
|
|
def set_data(self, data):
|
|
|
|
self.header['emisor'] = data['emisor']
|
|
|
|
self.header['title'] = data['title']
|
|
|
|
cols = len(data['rows'][0])
|
|
|
|
widths = []
|
|
|
|
for w in data['widths']:
|
|
|
|
widths.append(float(w) * cm)
|
|
|
|
t_styles = [
|
|
|
|
('GRID', (0, 0), (-1, -1), 0.25, colors.darkred),
|
|
|
|
('FONTSIZE', (0, 0), (-1, 0), 9),
|
|
|
|
('BOX', (0, 0), (-1, 0), 1, colors.darkred),
|
2017-10-24 00:03:07 -05:00
|
|
|
('TEXTCOLOR', (0, 0), (-1, 0), colors.black),
|
2017-10-23 00:45:41 -05:00
|
|
|
('FONTSIZE', (0, 1), (-1, -1), 8),
|
|
|
|
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
|
|
|
|
('ALIGN', (0, 0), (0, -1), 'RIGHT'),
|
|
|
|
]
|
|
|
|
if cols == 6:
|
|
|
|
t_styles += [
|
|
|
|
('ALIGN', (1, 1), (1, -1), 'CENTER'),
|
|
|
|
('ALIGN', (3, 1), (3, -1), 'CENTER'),
|
|
|
|
('ALIGN', (4, 1), (4, -1), 'RIGHT'),
|
|
|
|
]
|
|
|
|
elif cols == 3:
|
|
|
|
t_styles += [
|
|
|
|
('ALIGN', (-1, 0), (-1, -1), 'RIGHT'),
|
|
|
|
('ALIGN', (-2, 0), (-2, -1), 'RIGHT'),
|
|
|
|
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
|
|
|
|
]
|
|
|
|
elif cols == 2:
|
|
|
|
t_styles += [
|
|
|
|
('ALIGN', (-1, 0), (-1, -1), 'RIGHT'),
|
|
|
|
('ALIGN', (0, 0), (-1, 0), 'CENTER'),
|
|
|
|
]
|
|
|
|
rows = []
|
|
|
|
for i, r in enumerate(data['rows']):
|
|
|
|
n = i + 1
|
|
|
|
rows.append(('{}.-'.format(n),) + r)
|
|
|
|
if cols == 6:
|
|
|
|
if r[4] == 'Cancelado':
|
|
|
|
t_styles += [
|
|
|
|
('GRID', (0, n), (-1, n), 0.25, colors.red),
|
|
|
|
('TEXTCOLOR', (0, n), (-1, n), colors.red),
|
|
|
|
]
|
|
|
|
rows.insert(0, data['titles'])
|
|
|
|
t = Table(rows, colWidths=widths, repeatRows=1)
|
|
|
|
t.setStyle(TableStyle(t_styles))
|
|
|
|
|
|
|
|
text = 'Total este reporte = $ {}'.format(data['total'])
|
|
|
|
ps = ParagraphStyle(
|
|
|
|
name='Total',
|
|
|
|
fontSize=12,
|
|
|
|
fontName='Helvetica-BoldOblique',
|
|
|
|
textColor=colors.darkred,
|
|
|
|
spaceBefore=0.5 * cm,
|
|
|
|
spaceAfter=0.5 * cm)
|
|
|
|
p1 = Paragraph(text, ps)
|
|
|
|
text = 'Nota: esta suma no incluye documentos cancelados'
|
|
|
|
ps = ParagraphStyle(
|
|
|
|
name='Note',
|
|
|
|
fontSize=7,
|
|
|
|
fontName='Helvetica-BoldOblique')
|
|
|
|
p2 = Paragraph(text, ps)
|
|
|
|
self.data = [t, p1, p2]
|
|
|
|
return
|
|
|
|
|
|
|
|
def render(self):
|
|
|
|
frame = Frame(self.leftMargin, self.bottomMargin,
|
|
|
|
self.width, self.height, id='normal')
|
|
|
|
template = PageTemplate(id='report', frames=frame)
|
|
|
|
self.addPageTemplates([template])
|
|
|
|
self.build(self.data, canvasmaker=NumberedCanvas)
|
|
|
|
return
|
|
|
|
|
|
|
|
|
2017-12-10 12:12:06 -06:00
|
|
|
class SeaFileAPI(object):
|
|
|
|
FILE_DOES_NOT_EXIST = 441
|
|
|
|
|
|
|
|
def __init__(self, url, username, password):
|
|
|
|
self._url = url
|
|
|
|
self._headers = self._get_auth(username, password)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def is_connect(self):
|
|
|
|
return bool(self._headers)
|
|
|
|
|
|
|
|
def _open(self, path):
|
|
|
|
return open(path, 'rb')
|
|
|
|
|
|
|
|
def _get_auth(self, username, password):
|
|
|
|
url = self._url + 'auth-token/'
|
|
|
|
data = {
|
|
|
|
'username': username,
|
|
|
|
'password': password,
|
|
|
|
}
|
|
|
|
resp = requests.post(url, data=data)
|
|
|
|
if resp.status_code != requests.codes.ok:
|
|
|
|
msg = 'Token Error: {}'.format(resp.status_code)
|
|
|
|
print (msg)
|
|
|
|
return {}
|
|
|
|
|
|
|
|
headers = {'Authorization': 'Token {}'.format(resp.json()['token'])}
|
|
|
|
return headers
|
|
|
|
|
|
|
|
def _get_upload_link(self, repo_id):
|
|
|
|
if not self._headers:
|
|
|
|
return ''
|
|
|
|
|
|
|
|
url = '{}repos/{}/upload-link/'.format(self._url, repo_id)
|
|
|
|
resp = requests.get(url, headers=self._headers)
|
|
|
|
if resp.status_code != requests.codes.ok:
|
|
|
|
msg = 'Error: {}'.format(resp.status_code)
|
|
|
|
print (msg)
|
|
|
|
return ''
|
|
|
|
|
|
|
|
return resp.json()
|
|
|
|
|
|
|
|
def _get_update_link(self, repo_id):
|
|
|
|
if not self._headers:
|
|
|
|
return ''
|
|
|
|
|
|
|
|
url = '{}repos/{}/update-link/'.format(self._url, repo_id)
|
|
|
|
data = {'p': '/'}
|
|
|
|
resp = requests.get(url, data=data, headers=self._headers)
|
|
|
|
if resp.status_code != requests.codes.ok:
|
|
|
|
msg = 'Error: {}'.format(resp.status_code)
|
|
|
|
print (msg)
|
|
|
|
return ''
|
|
|
|
|
|
|
|
return resp.json()
|
|
|
|
|
|
|
|
def _decrypt(self, repo_id, password):
|
|
|
|
if not self._headers:
|
|
|
|
return False
|
|
|
|
|
|
|
|
url = '{}repos/{}/'.format(self._url, repo_id)
|
|
|
|
data = {'password': password}
|
|
|
|
resp = requests.post(url, data=data, headers=self._headers)
|
|
|
|
if resp.status_code != requests.codes.ok:
|
|
|
|
msg = 'Error: {}'.format(resp.status_code)
|
|
|
|
print (msg)
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
2017-12-10 14:35:03 -06:00
|
|
|
def upload_file(self, path_file, repo_id, relative_path, password=''):
|
2017-12-10 12:12:06 -06:00
|
|
|
if not self._headers:
|
|
|
|
return False
|
|
|
|
|
|
|
|
if password:
|
|
|
|
if not self._decrypt(repo_id, password):
|
|
|
|
return False
|
|
|
|
|
|
|
|
upload_link = self._get_upload_link(repo_id)
|
2017-12-10 19:45:19 -06:00
|
|
|
if isinstance(path_file, tuple):
|
|
|
|
obj_file = path_file[0]
|
|
|
|
filename = path_file[1]
|
|
|
|
else:
|
|
|
|
obj_file = self._open(path_file)
|
|
|
|
_, filename = self._info_path(path_file)
|
|
|
|
|
2017-12-10 12:12:06 -06:00
|
|
|
data = {
|
2017-12-10 19:45:19 -06:00
|
|
|
# ~ 'filename': filename,
|
2017-12-10 14:35:03 -06:00
|
|
|
'parent_dir': relative_path,
|
2017-12-10 12:12:06 -06:00
|
|
|
'relative_path': '',
|
|
|
|
}
|
2017-12-10 19:45:19 -06:00
|
|
|
files = {'file': (filename, obj_file)}
|
2017-12-10 12:12:06 -06:00
|
|
|
|
|
|
|
resp = requests.post(
|
|
|
|
upload_link, data=data, files=files, headers=self._headers)
|
|
|
|
if resp.status_code != requests.codes.ok:
|
|
|
|
msg = 'Upload Code: {}\n{}'.format(resp.status_code, resp.text)
|
|
|
|
print (msg)
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
def _info_path(self, path):
|
|
|
|
path, filename = os.path.split(path)
|
|
|
|
return path, filename
|
|
|
|
|
2017-12-10 14:35:03 -06:00
|
|
|
def list_directory(self, repo_id):
|
2017-12-10 12:12:06 -06:00
|
|
|
if not self._headers:
|
|
|
|
return False
|
|
|
|
|
2017-12-10 14:35:03 -06:00
|
|
|
url = '{}repos/{}/dir/'.format(self._url, repo_id)
|
|
|
|
params = {'p': '/', 't': 'd', 'recursive': '1'}
|
|
|
|
try:
|
|
|
|
resp = requests.get(url, params=params, headers=self._headers)
|
|
|
|
return resp.json()
|
|
|
|
except Exception as e:
|
|
|
|
return False
|
|
|
|
|
|
|
|
def create_dir(self, repo_id, name):
|
|
|
|
url = '{}repos/{}/dir/'.format(self._url, repo_id)
|
|
|
|
data = {'operation': 'mkdir'}
|
|
|
|
params = {'p': name}
|
|
|
|
resp = requests.post(url, data=data, headers=self._headers, params=params)
|
|
|
|
if resp.status_code != requests.codes.created:
|
|
|
|
msg = 'Create Dir Error: {}'.format(resp.status_code)
|
|
|
|
print (msg)
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
def _validate_directory(self, repo_id, target_file):
|
|
|
|
if target_file == '/':
|
|
|
|
return True
|
|
|
|
|
|
|
|
names = target_file.split('/')[:-1]
|
|
|
|
directories = self.list_directory(repo_id)
|
|
|
|
|
|
|
|
if isinstance(directories, bool):
|
|
|
|
return False
|
|
|
|
|
|
|
|
exists = False
|
|
|
|
parent_dir = '/'
|
|
|
|
name_dir = ''
|
|
|
|
for name in names:
|
|
|
|
name_dir += '/' + name
|
|
|
|
for directory in directories:
|
|
|
|
if name == directory['name'] and parent_dir == directory['parent_dir']:
|
|
|
|
exists = True
|
|
|
|
break
|
|
|
|
if exists:
|
|
|
|
exists = False
|
|
|
|
else:
|
|
|
|
self.create_dir(repo_id, name_dir)
|
|
|
|
if parent_dir == '/':
|
|
|
|
parent_dir += name
|
|
|
|
else:
|
|
|
|
parent_dir += '/' + name
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
def update_file(self, path_file, repo_id, target_file, password, create=True):
|
|
|
|
if not self._headers:
|
|
|
|
return False
|
|
|
|
|
|
|
|
if not self._validate_directory(repo_id, target_file):
|
|
|
|
return False
|
|
|
|
|
2017-12-10 12:12:06 -06:00
|
|
|
if password:
|
|
|
|
if not self._decrypt(repo_id, password):
|
|
|
|
return False
|
|
|
|
|
|
|
|
update_link = self._get_update_link(repo_id)
|
2017-12-10 19:45:19 -06:00
|
|
|
if isinstance(path_file, tuple):
|
|
|
|
obj_file = path_file[0]
|
|
|
|
filename = path_file[1]
|
|
|
|
else:
|
|
|
|
obj_file = self._open(path_file)
|
|
|
|
_, filename = self._info_path(path_file)
|
|
|
|
|
2017-12-10 12:12:06 -06:00
|
|
|
files = {
|
2017-12-10 19:45:19 -06:00
|
|
|
'file': (filename, obj_file),
|
2017-12-10 12:12:06 -06:00
|
|
|
'filename': (None, filename),
|
2017-12-10 14:35:03 -06:00
|
|
|
'target_file': (None, '{}{}'.format(target_file, filename))
|
2017-12-10 12:12:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
resp = requests.post(update_link, files=files, headers=self._headers)
|
|
|
|
if resp.status_code != requests.codes.ok:
|
|
|
|
msg = 'Update Code: {}\n{}'.format(resp.status_code, resp.text)
|
|
|
|
print (msg)
|
|
|
|
if resp.status_code == self.FILE_DOES_NOT_EXIST and create:
|
2017-12-10 14:35:03 -06:00
|
|
|
relative_path = '/'
|
|
|
|
if target_file != '/':
|
|
|
|
relative_path += target_file
|
|
|
|
return self.upload_file(path_file, repo_id, relative_path, password)
|
2017-12-10 12:12:06 -06:00
|
|
|
else:
|
|
|
|
return False
|
|
|
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
def ping(self):
|
|
|
|
url = self._url + 'ping/'
|
|
|
|
resp = requests.get(url)
|
|
|
|
return resp.json()
|
|
|
|
|
|
|
|
|
|
|
|
def auth_ping(self):
|
|
|
|
url = self._url + 'auth/ping/'
|
|
|
|
resp = requests.get(url, headers=self._headers)
|
|
|
|
return resp.json()
|
|
|
|
|
2018-01-01 02:14:30 -06:00
|
|
|
|
|
|
|
class PrintTicket(object):
|
|
|
|
LINE = '------------------------------------------------\n'
|
|
|
|
TITLES = 'CANT. U ARTICULO P.U. TOTAL\n'
|
|
|
|
LEYENDA = 'GRACIAS POR SU COMPRA\n\nGuarde este ticket para cualquier ' \
|
|
|
|
'aclaración.\nComprobante simplificado de operación con\npúblico en ' \
|
|
|
|
'general de acuerdo al Art. 37\nFracc II inc. v del Reglamento del\n' \
|
|
|
|
'Código Fiscal de la Federación.\n\n'
|
|
|
|
|
|
|
|
def __init__(self, info):
|
|
|
|
self.p = self._init_printer(info)
|
|
|
|
|
|
|
|
def _init_printer(self, info):
|
|
|
|
try:
|
|
|
|
if info['ip']:
|
|
|
|
p = printer.Network(info['ip'])
|
|
|
|
else:
|
|
|
|
p = printer.Usb(*info['usb'])
|
|
|
|
p.codepage = 'cp850'
|
|
|
|
return p
|
|
|
|
except Exception as e:
|
|
|
|
print (e)
|
|
|
|
return
|
|
|
|
|
|
|
|
def _set(self, *data):
|
|
|
|
self.p.set(*data)
|
|
|
|
return
|
|
|
|
|
|
|
|
def _t(self, text):
|
|
|
|
self.p.text(text)
|
|
|
|
return
|
|
|
|
|
|
|
|
def _l(self):
|
|
|
|
self._t(self.LINE)
|
|
|
|
return
|
|
|
|
|
|
|
|
def printer(self, data):
|
|
|
|
if self.p is None:
|
|
|
|
return False
|
|
|
|
|
|
|
|
self._emisor(data['emisor'])
|
|
|
|
self._receptor(data['receptor'])
|
|
|
|
self._ticket(data['ticket'])
|
|
|
|
self._products(data['products'])
|
|
|
|
self._footer(data['ticket'])
|
|
|
|
self.p.cut()
|
|
|
|
return True
|
|
|
|
|
|
|
|
def _emisor(self, data):
|
|
|
|
self._set('center', 'B', 'B', 2, 2)
|
|
|
|
self._t(data['name'])
|
|
|
|
self._set('center', 'B', 'B', 2)
|
|
|
|
self._t(data['rfc'])
|
|
|
|
self._set('center', 'A')
|
2018-01-10 23:21:37 -06:00
|
|
|
self._t(data['regimen'])
|
2018-01-01 02:14:30 -06:00
|
|
|
self._t(data['address'])
|
|
|
|
return
|
|
|
|
|
|
|
|
def _receptor(self, data):
|
|
|
|
self._set('center', 'B', 'B', 2)
|
|
|
|
self._t(data['name'])
|
|
|
|
return
|
|
|
|
|
|
|
|
def _ticket(self, data):
|
|
|
|
self._set('left', 'B', 'B', 2)
|
|
|
|
self._t(data['title'])
|
|
|
|
self._set('left', 'A')
|
|
|
|
self._t(data['date'])
|
|
|
|
return
|
|
|
|
|
|
|
|
def _products(self, data):
|
|
|
|
self._l()
|
|
|
|
self._t(self.TITLES)
|
|
|
|
self._l()
|
|
|
|
for p in data:
|
|
|
|
self._t(p)
|
|
|
|
self._l()
|
|
|
|
self._t('Total artículos: {}\n'.format(len(data)))
|
|
|
|
self._l()
|
|
|
|
return
|
|
|
|
|
|
|
|
def _footer(self, data):
|
|
|
|
self._set('right', 'B', 'B', 2)
|
|
|
|
self._t(data['total'])
|
|
|
|
self._set('center', 'A')
|
|
|
|
self._t(data['letters'])
|
|
|
|
self._t(self.LEYENDA)
|
|
|
|
self._set('center', 'A', 'B')
|
|
|
|
self._t('empresalibre.net')
|
2018-10-27 01:15:20 -05:00
|
|
|
return
|