diff --git a/source/app/controllers/helper.py b/source/app/controllers/helper.py index b402199..0cd1c36 100644 --- a/source/app/controllers/helper.py +++ b/source/app/controllers/helper.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -#~ import falcon +import os import re import smtplib import ssl @@ -13,8 +13,8 @@ from email.mime.text import MIMEText from email import encoders from email.utils import formatdate -import os import requests +from escpos import printer from reportlab.platypus import BaseDocTemplate, Frame, PageTemplate, Image from reportlab.lib import colors @@ -949,3 +949,92 @@ class SeaFileAPI(object): 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['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 \ No newline at end of file diff --git a/source/app/controllers/util.py b/source/app/controllers/util.py index 6e17cec..8ae6395 100644 --- a/source/app/controllers/util.py +++ b/source/app/controllers/util.py @@ -36,7 +36,7 @@ import pyqrcode from dateutil import parser from .helper import CaseInsensitiveDict, NumLet, SendMail, TemplateInvoice, \ - SeaFileAPI + SeaFileAPI, PrintTicket from settings import DEBUG, MV, log, template_lookup, COMPANIES, DB_SAT, \ PATH_XSLT, PATH_XSLTPROC, PATH_OPENSSL, PATH_TEMPLATES, PATH_MEDIA, PRE, \ PATH_XMLSEC, TEMPLATE_CANCEL, DEFAULT_SAT_PRODUCTO, DECIMALES, DIR_FACTURAS @@ -1705,12 +1705,12 @@ class ImportFacturaLibreGambas(object): ('cfdifacturas', 'Facturas'), ('categorias', 'Categorias'), ('productos', 'Productos'), - # ~ ('tickets', 'Tickets'), + ('tickets', 'Tickets'), ) for source, target in tables: data[target] = self._get_table(source) - # ~ data['Socios'] += self._clientes + data['Socios'] += self._clientes return data @@ -2505,3 +2505,8 @@ class ImportFacturaLibre(object): data.append(new) return data + + +def print_ticket(data, info): + p = PrintTicket(info) + return p.printer(data) diff --git a/source/app/models/db.py b/source/app/models/db.py index 7ec35a0..d88373c 100644 --- a/source/app/models/db.py +++ b/source/app/models/db.py @@ -268,6 +268,8 @@ class StorageEngine(object): return main.Tickets.cancel(values) if opt == 'invoice': return main.Tickets.invoice(values) + if opt == 'print': + return main.Tickets.printer(values) def get_tickets(self, values): return main.Tickets.get_by(values) diff --git a/source/app/models/main.py b/source/app/models/main.py index a9bc1c5..bcf762b 100644 --- a/source/app/models/main.py +++ b/source/app/models/main.py @@ -17,7 +17,7 @@ if __name__ == '__main__': from controllers import util from settings import log, VERSION, PATH_CP, COMPANIES, PRE, CURRENT_CFDI, \ INIT_VALUES, DEFAULT_PASSWORD, DECIMALES, IMPUESTOS, DEFAULT_SAT_PRODUCTO, \ - CANCEL_SIGNATURE + CANCEL_SIGNATURE, PUBLIC FORMAT = '{0:.2f}' @@ -4328,6 +4328,63 @@ class Tickets(BaseModel): doc = util.to_pdf(data, data['emisor']['rfc']) return doc, name + def _format_ticket(self, id): + emisor = util.get_dict(Emisor.select().dicts()[0]) + ticket = Tickets.select().where(Tickets.id==id).dicts()[0] + products = TicketsDetalle.get_by_print(id) + + emisor['name'] = '{}\n'.format(emisor['nombre']) + emisor['rfc'] = 'RFC: {}\n'.format(emisor['rfc']) + interior = '' + if emisor['no_interior']: + interior = ', {}'.format(emisor['no_interior']) + colonia = '' + if emisor['colonia']: + colonia = ', Col. {}'.format(emisor['colonia']) + municipio = '' + if emisor['municipio']: + municipio = ', {}'.format(emisor['municipio']) + estado = '' + if emisor['estado']: + estado = ', {}'.format(emisor['estado']) + cp = '' + if emisor['codigo_postal']: + cp = ', C.P. {}'.format(emisor['codigo_postal']) + pais = '' + if emisor['pais']: + pais = ', {}'.format(emisor['pais']) + direccion = '{} {}{}{}{}{}{}{}\n\n'.format(emisor['calle'], + emisor['no_exterior'], interior, colonia, municipio, estado, cp, + pais) + emisor['address'] = direccion + + ticket['title'] = 'Ticket: {}{}\n\n'.format(ticket['serie'], + ticket['folio']) + ticket['date'] = 'Fecha y hora: {}\n'.format(ticket['fecha']) + ticket['letters'] = '{}\n\n'.format( + util.to_letters(ticket['total'], 'peso')) + ticket['total'] = 'TOTAL: $ {:>12,.2f}\n\n'.format(ticket['total']) + + data = { + 'emisor': emisor, + 'receptor': {'name': '{}\n\n'.format(PUBLIC)}, + 'ticket': ticket, + 'products': products, + } + return data + + @classmethod + def printer(cls, values): + id = int(values['id']) + info = {'ip': '', 'usb': (int('1ba0', 16), int('2204', 16))} + data = cls._format_ticket(cls, id) + result = util.print_ticket(data, info) + msg = 'Ticket impreso correctamente' + if not result: + msg = 'Asegurate de que la impresora este conectada y funcionando.' + result = {'ok': result, 'msg': msg} + return result + class TicketsDetalle(BaseModel): ticket = ForeignKeyField(Tickets) @@ -4356,6 +4413,21 @@ class TicketsDetalle(BaseModel): return precio_final + @classmethod + def get_by_print(cls, id): + products = TicketsDetalle.select().where(TicketsDetalle.ticket==id) + lines = [] + for p in products: + price_with_tax = cls._with_tax(cls, p) + importe = round(price_with_tax * float(p.cantidad), DECIMALES) + l = '{:>6,.2f} {:<4} {:<14} {:>9,.2f} {:>10,.2f}\n'.format( + p.cantidad, p.producto.unidad.name, p.descripcion, + price_with_tax, importe + ) + lines.append(l) + + return lines + @classmethod def get_by_ticket(cls, id): data = [] diff --git a/source/app/settings.py b/source/app/settings.py index 1c96b5f..c1298c4 100644 --- a/source/app/settings.py +++ b/source/app/settings.py @@ -117,4 +117,5 @@ IMPUESTOS = { DEFAULT_SAT_PRODUCTO = '01010101' DIR_FACTURAS = 'facturas' USAR_TOKEN = False -CANCEL_SIGNATURE = False \ No newline at end of file +CANCEL_SIGNATURE = False +PUBLIC = 'Público en general' \ No newline at end of file diff --git a/source/static/js/controller/tickets.js b/source/static/js/controller/tickets.js index f920c39..0ef7579 100644 --- a/source/static/js/controller/tickets.js +++ b/source/static/js/controller/tickets.js @@ -651,11 +651,38 @@ function ticket_notes_key_up(){ } +function print_ticket(id){ + var data = new Object() + data['opt'] = 'print' + data['id'] = id + + webix.ajax().post('tickets', data, { + error:function(text, data, XmlHttpRequest){ + msg = 'Ocurrio un error, consulta a soporte técnico' + msg_error(msg) + }, + success:function(text, data, XmlHttpRequest){ + values = data.json(); + if(values.ok){ + msg_ok(values.msg) + }else{ + msg_error(values.msg) + } + } + }) +} + + function grid_tickets_click(id, e, node){ - //~ var row = this.getItem(id) if(id.column == 'pdf'){ //~ window.open('/doc/tpdf/' + id, '_blank') - get_ticket_pdf(id) + get_ticket_pdf(id.row) + return + } + + if(id.column == 'print'){ + print_ticket(id.row) + return } } \ No newline at end of file diff --git a/source/static/js/ui/tickets.js b/source/static/js/ui/tickets.js index f3a8b67..2c5a9f4 100644 --- a/source/static/js/ui/tickets.js +++ b/source/static/js/ui/tickets.js @@ -38,7 +38,7 @@ var grid_tickets_cols = [ {id: "cliente", header: ["Razón Social", {content: "selectFilter"}], fillspace:true, sort:"string", hidden: true}, {id: 'pdf', header: 'PDF', adjust: 'data', template: get_icon('pdf')}, - {id: 'print', header: 'I', adjust: 'data', template: get_icon('print')}, + {id: 'print', header: '', adjust: 'data', template: get_icon('print')}, ]