empresa-libre/source/app/controllers/helper.py

1080 lines
36 KiB
Python
Raw Normal View History

2017-06-28 23:55:53 -05:00
#!/usr/bin/env python3
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
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
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)
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
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', {})
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)
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'])
p = Paragraph(data[k], ps)
ls.append(p)
cbb = Image(data['path_cbb'])
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):
2017-10-24 00:03:07 -05:00
#~ print (values)
2017-10-23 00:45:41 -05:00
self._data = values
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'])
self._rows = [Spacer(0, 6*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