#!/usr/bin/env python3 import os import re import smtplib import ssl import collections from collections import OrderedDict 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 import requests try: from escpos import printer except ImportError: printer = None from reportlab.platypus import BaseDocTemplate, Frame, PageTemplate, Image 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 #~ 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 = 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() 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.', } texto_inicial = '-(' texto_final = '/100 {})-'.format(tf.get(currency, currency)) 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' 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: if self._config['ssl'] and ( 'gmail' in self._config['servidor'] or 'outlook' in self._config['servidor']): self._server = smtplib.SMTP( self._config['servidor'], self._config['puerto'], timeout=10) self._server.ehlo() self._server.starttls() self._server.ehlo() elif self._config['ssl']: self._server = smtplib.SMTP_SSL( self._config['servidor'], self._config['puerto'], timeout=10) self._server.ehlo() 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 # ~ print (e) 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) if options['confirmar']: message['Disposition-Notification-To'] = message['From'] message.attach(MIMEText(options['mensaje'], 'html')) for f in options['files']: part = MIMEBase('application', 'octet-stream') if isinstance(f[0], str): part.set_payload(f[0].encode('utf-8')) else: part.set_payload(f[0]) 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 class NumberedCanvas(canvas.Canvas): X = 20.59 * cm XC = 21.6 * cm / 2 + 1.5 * cm Y = 1.5 * cm 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) # ~ self.setFillColor(colors.darkred) text = f'Página {self._pageNumber} de {page_count}' self.drawRightString(self.X, self.Y, text) text = 'Este documento es una representación impresa de un CFDI' self.drawCentredString(self.XC, self.Y, text) text = 'Factura elaborada con software libre' self.drawString(1.5 * cm, 1.5 * cm, text) 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): text = styles.pop('valor', '') if not value: value = text 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): logo_path = data.pop('logo', '') logo_style = styles.pop('logo', {}) logo_path2 = data.pop('logo2', '') logo_style2 = styles.pop('logo2', {}) 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) 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) return def _receptor(self, styles, data): title = styles.pop('titulo', {}) 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() return def _comprobante1(self, styles, data): title = styles.pop('titulo', {}) self.canv.setTitle(f"Factura {data.get('seriefolio', '')}") 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() return def afterPage(self): encabezado = self._custom_styles['encabezado'] self.canv.saveState() self._emisor(encabezado['emisor'], self._data['emisor']) self._receptor(encabezado['receptor'], self._data['receptor']) self._comprobante1(encabezado['comprobante'], self._data['comprobante']) self.canv.restoreState() return 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) if field == 'descripcion': html = '{}' style_bt = getSampleStyleSheet()['BodyText'] return Paragraph(html.format(value), style_bt) 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), ('BACKGROUND', (1, 0), (-1, -1), colors.lightgrey), ('TEXTCOLOR', (1, 0), (-1, -1), colors.black), ('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): leyendas = styles.pop('leyendas', {}) 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 = '{}' html = '{}' 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'), ('BACKGROUND', (1, 1), (1, 1), colors.lightgrey), ('TEXTCOLOR', (1, 1), (1, 1), colors.darkred), ('FACE', (1, 2), (1, 2), 'Helvetica-Bold'), ('BACKGROUND', (1, 3), (1, 3), colors.lightgrey), ('TEXTCOLOR', (1, 3), (1, 3), colors.darkred), ('FACE', (1, 4), (1, 4), 'Helvetica-Bold'), ('BACKGROUND', (1, 5), (1, 5), colors.lightgrey), ('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) 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) return ls @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): #~ print (values) self._data = values 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'), ('BACKGROUND', (0, 0), (-1, 0), colors.grey), ('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'), ('LINEBELOW', (0, 1), (-1, -1), 0.05 * cm, colors.grey), ('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 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), ('TEXTCOLOR', (0, 0), (-1, 0), colors.black), ('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 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 def upload_file(self, path_file, repo_id, relative_path, password=''): 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) 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) data = { # ~ 'filename': filename, 'parent_dir': relative_path, 'relative_path': '', } files = {'file': (filename, obj_file)} 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 def list_directory(self, repo_id): if not self._headers: return False 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 if password: if not self._decrypt(repo_id, password): return False update_link = self._get_update_link(repo_id) 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) files = { 'file': (filename, obj_file), 'filename': (None, filename), 'target_file': (None, '{}{}'.format(target_file, filename)) } 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: relative_path = '/' if target_file != '/': relative_path += target_file return self.upload_file(path_file, repo_id, relative_path, password) 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() 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') self._t(data['regimen']) 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') return