# This file is part of Tryton. The COPYRIGHT file at the top level of # this repository contains the full copyright notices and license terms. import os from io import StringIO import logging from datetime import datetime from decimal import Decimal pyudev = None try: import pyudev except: logging.warning("Pyudev module not found!") try: from escpos import printer except: logging.warning("Escpos module not found!") try: import cups except: logging.warning("Cups module not found!") __all__ = ['Receipt'] _ROW_CHARACTERS = 48 _DIGITS = 9 _PRINT_TAX_ID = False _DIGITS_CODE_RECEIPT = 4 # ------------------- Type Font Escpos ----------------- _FONT_A = 'a' # Normal Font _FONT_B = 'b' # Condensed Font # ------------------------------------------------------ if os.name == 'posix': homex = 'HOME' dirconfig = '.tryton/temp' elif os.name == 'nt': homex = 'USERPROFILE' dirconfig = 'AppData/Local/tryton/temp' HOME_DIR = os.getenv(homex) directory = os.path.join(HOME_DIR, dirconfig) if not os.path.exists(directory): os.makedirs(directory) TEMP_INVOICE_FILE = os.path.join(directory, 'invoice.txt') SSH_PORT = 23 def money(value): if type(value) != int: value = int(value) return '{:,}'.format(value) dev_printers = {} if pyudev: context = pyudev.Context() # for device in context.list_devices(): # if device.subsystem == 'usbmisc': # print(device.subsystem, device.sys_path.split('2-1/')[1][0:5], device.device_node) # dev_printers[str(device.sys_path.split('2-1/')[1][0:5])] = device.device_node class Receipt(object): __name__ = 'frontend_pos.ticket' def __init__(self, context, row_characters=None, logo=None, environment='retail'): self.logger = logging.getLogger('reporting') self._company = context.get('company') self._sale_device = context.get('sale_device') self._shop = context.get('shop') self._street = context.get('street') self._city = context.get('city') self._phone = context.get('phone') self._id_number = context.get('id_number') self._regime_tax = context.get('regime_tax') self._gta_info = context.get('gta_info') self._user = context.get('user') self._footer = context.get('footer') self._header = context.get('header') self._printing_taxes = context.get('printing_taxes') self._delta_locale = context.get('delta_locale') self._environment = environment self._row_characters = _ROW_CHARACTERS if context.get('row_characters'): self._row_characters = int(context.get('row_characters')) self.taxes_col_width = int(self._row_characters / 3) order_col_width = int(self._row_characters / 3) self.order_col_1 = order_col_width - 6 self.order_col_2 = order_col_width + 11 self.order_col_3 = order_col_width - 5 self._show_position = context.get('show_position') self._show_discount = context.get('show_discount') self._img_logo = None if logo: self._img_logo = StringIO(logo) def printer_found(self): return self._printer def printing(f): def p(self, *p, **kw): self._open_device() try: res = f(self, *p, **kw) finally: pass return res return p def test_printer(self): if self._interface == 'usb': if os.name == 'posix': self._printer = printer.File(self._device) elif os.name == 'nt': self._printer = printer.UsbWin(self._device) self._printer.open() elif self._interface == 'network': self._printer = printer.Network(self._device) elif self._interface == 'ssh': self._printer = printer.FileSSH(*self._device.split('@')) self._printer.open() if not self._printer: return if self._img_logo: self.print_logo() self.print_enter() self.print_enter() self.print_header() self.print_enter() self.print_enter() self._printer.cut() self._printer.cashdraw(2) self._printer.close() def set_printer(self, printer): if dev_printers.get(printer['device']): device = dev_printers[printer['device']] else: device = printer['device'] self._interface = printer['interface'] self._device = device def print_sale(self, sale): try: if self._interface == 'usb': if os.name == 'posix': self._printer = printer.File(self._device) elif os.name == 'nt': self._printer = printer.UsbWin(self._device) self._printer.open() elif self._interface == 'network': self._printer = printer.Network(self._device) elif self._interface == 'ssh': self._printer = printer.FileSSH(*self._device.split('@')) self._printer.open() elif self._interface == 'cups': self.conn = cups.Connection() self._file = open(TEMP_INVOICE_FILE, 'w') self._printer = CupsPrinter(self._file, self._row_characters) if not self._printer: self.logger.info("Warning: Can not found Printer!") return self.logger.info("Info: Printer is OK!") self._print_sale(sale) except: self.logger.info("Warning: Printer error or device not found!") def _print_sale(self, sale): self.print_header() self.print_body(sale) self.print_footer() # self.print_extra_info(sale) if self._interface in ['usb', 'ssh', 'network']: self._printer.close() elif self._interface == 'cups': self._file.close() self.conn.printFile(self._printer_name, TEMP_INVOICE_FILE, 'POS Invoice', {}) def print_logo(self): self._printer.set(align='center') self._printer.image(self._img_logo) self.print_enter() def print_header(self): if self._img_logo: self.print_logo() self._printer.set(align='center') if self._header != '' and self._header is not None: self._printer.text(self._header) self.print_enter() self._printer.text(self._company) self.print_enter() self._printer.text(self._shop) self.print_enter() if self._id_number: self._printer.text('NIT:' + self._id_number) if self._regime_tax: self._printer.text(' ' + self._regime_tax) self.print_enter() if self._street: self._printer.text(self._street) self.print_enter() if self._city: self._printer.text(self._city) if self._phone: if self._city: self._printer.text(' ') self._printer.text('Telefono:' + self._phone) if self._city or self._phone: self.print_enter() self.print_enter() self.print_enter() def print_horinzontal_line(self): self._printer.text('-' * self._row_characters) def print_horinzontal_double_line(self): self._printer.text('=' * self._row_characters) def print_enter(self): self._printer.text('\n') def print_split(self, left, right): len_left = self._row_characters - len(right) - 1 left = left[:len_left] if type(left) == bytes: left = left.decode("utf-8") if type(right) == bytes: right = right.decode("utf-8") left += (len_left - len(left) + 1) * ' ' self._printer.text(left) self._printer.text(right + '\n') def print_body(self, sale): self._cashdraw = True self._printer.set(font=_FONT_B) self._printer.set(align='left') if sale['number'] and sale['state'] in ['processing', 'done', 'cancel']: if sale['total_amount'] >= 0: self._printer.text('FACTURA DE VENTA No. ' + sale['number']) else: self._printer.text('NOTA CREDITO No. ' + sale['number']) else: self._cashdraw = False self._printer.text('Pedido: ' + sale['order']) self.print_enter() #mod_hours = sale["create_date"] + timedelta(hours=self._delta_locale) #time_ = mod_hours.strftime('%I:%M %p') self._printer.text('Fecha:%s' % sale['date']) if sale.get('turn') and sale['turn'] != 0: self._printer.text('Turno: %s - ' % str(sale['turn'])) self.print_enter() self.print_horinzontal_line() party_name = 'Cliente: %s ' % sale['party'] party_id_number = 'Id: %s' % sale.get('party_id_number', '') if len(party_name + party_id_number) > self._row_characters: self._printer.text(party_name) self.print_enter() self._printer.text(party_id_number) else: self._printer.text(party_name + party_id_number) if sale.get('party_address'): self.print_enter() self._printer.text('Direccion: %s' % sale['party_address']) if sale.get('party_phone'): self.print_enter() self._printer.text('Telefono: %s' % sale['party_phone']) self.print_enter() self.print_horinzontal_line() self.print_split(' Articulo ', 'Subtotal ') self.print_horinzontal_line() len_row = self._row_characters - (_DIGITS_CODE_RECEIPT + 1) - (_DIGITS + 1) for line in sale['lines']: if line['taxes'] and _PRINT_TAX_ID: tax_id = ' ' + str(line['taxes'][0].id) else: tax_id = '' line_total = money(line['amount_w_tax']) + tax_id if line['quantity'] != 1: length_name = self._row_characters - 11 first_line = line['code'] + ' ' + line['name'][:length_name] if type(first_line) == bytes: first_line = first_line.decode('utf-8') self._printer.text(first_line + '\n') unit_price_w_tax = str(round(line['unit_price_w_tax'], 2)) second_line = ' %s x %s' % (line['quantity'], unit_price_w_tax) second_line = second_line.encode('utf-8') self.print_split(second_line, line_total) else: if self._environment == 'retail': line_pt = line['code'] + ' ' + line['name'][:len_row] else: line_pt = line['name'][:len_row] self.print_split(line_pt, line_total) untaxed_amount = sale['untaxed_amount'] total_amount = sale['total_amount'] total_string = 'Total:' tip = None if sale.get('tip') and sale['tip'] > 0: untaxed_amount = untaxed_amount - sale['tip'] total_amount = untaxed_amount + sale['tax_amount'] tip = sale['tip'] total_string = 'Total Sin Propina:' self.print_split('', '----------------') self.print_split('Subtotal Base:', money(untaxed_amount)) self.print_split('Impuesto:', money(sale['tax_amount'])) self.print_split('', '----------------') self.print_split(total_string, money(total_amount)) self.print_enter() if tip: self.print_split('Propina:', money(tip)) self.print_split('Total con Propina:', money(sale['total_amount'])) if self._show_discount: self.print_split('Descuento:', money(sale['discount'])) self.print_enter() if sale['cash_received']: self.print_split('Recibido:', money(sale['cash_received'])) else: self.print_split('Recibido:', money(sale['paid_amount'])) self.print_split('Saldo Pendiente:', money(sale['total_amount'] - sale['paid_amount'])) self.print_split('Cambio:', money(sale['change'])) self.print_horinzontal_line() self.print_enter() if self._printing_taxes: self.print_col('Tipo', self.taxes_col_width + 2) self.print_col('Base', self.taxes_col_width) self.print_col('Imp.', self.taxes_col_width) taxes = sale['taxes'] for tax in taxes: self.print_col(str(taxes[tax]['name']) + ' ', self.taxes_col_width) self.print_col(str(int(taxes[tax]['base'])), self.taxes_col_width) self.print_col(str(int(taxes[tax]['tax'])), self.taxes_col_width) self.print_enter() self.print_horinzontal_line() self.print_enter() no_products = 'No Items: %s' % str(sale['num_products']) self._printer.text(no_products) self.print_enter() if self._gta_info and sale['state'] not in ['draft']: self._printer.text(self._gta_info) self.print_enter() if sale['state'] in ['processing', 'done']: self._printer.text('Pedido: ' + sale['order']) self.print_enter() register = 'Caja No. %s' % self._sale_device self._printer.text(register) self.print_enter() self._printer.text('Cajero: %s' % self._user) self.print_enter() if sale.get('salesman'): self._printer.text('Vendedor: %s' % sale['salesman']) self.print_enter() if sale.get('comment'): self._printer.text('Notas: %s' % sale['comment']) self.print_enter() if self._show_position: self._printer.text('Posicion: %s' % str(sale['position'])) self.print_enter() self._printer.set(align='center') #self.print_split('Puntos Acumulados:', sale['points']) #self.print_enter() #printer.barcode(sale.receipt_code, 'CODE128B', 3, 50,'','') self.print_enter() self.print_enter() def print_extra_info(self, sale): if sale.get('pos_notes'): self.print_enter() self.print_header() self.print_horinzontal_line() self.print_enter() party_name = 'Cliente: %s ' % sale['party'] self._printer.text(party_name) self.print_enter() if self._show_position: self._printer.text('Posicion: %s' % str(sale['position'])) self.print_enter() if sale['state'] in ['draft']: self._printer.text('Cotizacion: ', sale['order']) else: self._printer.text('Factura No. ' + sale['number']) self.print_enter() self._printer.text(str(sale.get('pos_notes'))) self.print_enter() self.print_horinzontal_line() self.print_enter() self._printer.cut() def print_col(self, x, l): self._printer.text(x[:l] + (l - len(x)) * ' ') def print_footer(self, ): if self._footer: self._printer.text(self._footer) self.print_enter() self._printer.text('SOFTWARE POS TRYTON - www.presik.com') self.print_enter() self._printer.cut() if self._cashdraw: self._printer.cashdraw(2) self.print_enter() def print_orders(self, orders, reversion=None, kind='command'): res = [] self.order_kind = kind for order in orders.values(): try: if dev_printers.get(order['host']): host = dev_printers[order['host']] else: host = order['host'] if order['interface'] == 'usb': self._printer = printer.File(host) elif order['interface'] == 'network': self._printer = printer.Network(host) elif order['interface'] == 'ssh': self._printer = printer.FileSSH(*host.split('@')) if self._printer: self._printer.open() elif order['interface'] == 'cups': pass if not self._printer: self.logger.info("Warning: Interface not found for printer!") res.append(None) continue self.logger.info("Info: Printer is OK!") res.append(self._print_order(order, reversion)) except: self.logger.info("Warning: Can not found Printer!") res.append(None) return all(res) def _print_order(self, order, reversion): self.print_body_order(order, reversion) self._printer.cut() self._row_characters = order['row_characters'] if order['interface'] in ('network', 'usb', 'ssh'): self._printer.close() return True def print_body_order(self, order, reversion): self._printer.set(font=_FONT_B) self._printer.set(align='center') self._printer.text('TURNO: %s' % str(order['turn'])) self.print_enter() self.print_enter() kind = 'COMANDA' if self.order_kind == 'delivery': kind = 'PEDIDO' title = '+ + + + + %s + + + + +' % kind self._printer.text(title) self.print_enter() self._printer.set(align='left') self.print_enter() date_ = datetime.now().strftime("%Y-%m-%d %H:%M %p") self._printer.text('FECHA: ' + date_) self.print_enter() if self.order_kind == 'delivery': self._printer.text('FACTURA: ' + order['number']) self.print_enter() delivery_charge = 'Cliente' if order['delivery_charge'] == 'company': delivery_charge = 'Empresa' self._printer.text('CARGO DEL DOMICILIO: ' + delivery_charge) self.print_enter() if order.get('payment_term'): self._printer.text('FORMA DE PAGO: ' + order['payment_term']) self.print_enter() if order.get('sale_number'): self._printer.text('PEDIDO: %s' % str(order['sale_number'])) self.print_enter() self._printer.text('POSICION: %s' % str(order['position'])) self.print_enter() self._printer.text('VENDEDOR: %s' % order['salesman']) self.print_enter() self._printer.text('AMBIENTE: %s' % order['shop']) self.print_enter() self._printer.text('CLIENTE: %s' % order['party']) self.print_enter() if self.order_kind == 'delivery': self._printer.text('VALOR: ' + str(order['total_amount'])) self.print_enter() if order.get('pos_notes'): self._printer.text(order['pos_notes']) self.print_enter() self._printer.text('CAJA No: %s' % self._sale_device or '') self.print_enter() self.print_enter() self._printer.set(align='center') self.print_horinzontal_line() if not reversion and self.order_kind == 'command': self._printer.text('-------- PREPARAR Y SERVIR --------') elif not reversion and self.order_kind == 'delivery': self._printer.text('-------- ENTREGAR --------') else: self._printer.text('<<<< R E V E R S I O N >>>>') self.print_enter() if self.order_kind != 'delivery': self.print_horinzontal_line() self._printer.set(align='left') self.print_enter() self._printer.set(align='left') self.print_enter() self.print_horinzontal_line() self.print_col('CANT', self.order_col_1) self.print_col('PRODUCTO', self.order_col_2) self.print_col(' ' + 'PRECIO', self.order_col_3) for line in order['lines']: qty = str(int(Decimal(line['quantity']))) self.print_col(qty, self.order_col_1) self.print_col(line['name'], self.order_col_2) self.print_col(' ' + str(line['unit_price']), self.order_col_3) if line['note']: self.print_enter() self._printer.text(' ----->> NOTA: ' + line['note']) self.print_enter() self.print_enter() self.print_horinzontal_double_line() self.print_enter() self._printer.text('NOTA:') self.print_enter() if order['comment']: self._printer.text(str(order['comment'])) self.print_enter() self.print_horinzontal_line() self.print_enter() self.print_enter() class CupsPrinter(object): "Cups Printer" __name__ = 'sale_pos_frontend.cups_printer' def __init__(self, _file, row_characters): self._file = _file self.align = 'left' self._row_characters = row_characters def text(self, text): self._text(text) def set(self, align='left', font=_FONT_A): if align: self.align = align if font: self.font = font def cut(self): pass def cashdraw(number): pass def _text(self, text): start_spaces = '' if self.align == 'center': start_spaces = int((self._row_characters - len(text)) / 2) * ' ' elif self.align == 'right': start_spaces = int(self._row_characters - len(text)) * ' ' else: pass text = start_spaces + text self._file.write(text) if __name__ == '__main__': # Test for Escpos interface printer Linux # Network example device = 'network', '192.168.0.32' # Unix-like Usb example # device = 'usb','/dev/usb/lp1' # Windows Usb example for printer nameb SATPOS # device = 'usb', 'SATPOS' # SSH example # device = 'ssh', 'psk@xxxxx@192.168.0.5@23@/dev/usb/lp1' example_dev = { 'interface': device[0], 'device': device[1], } ctx_printing = {} ctx_printing['company'] = 'OSCORP INC' ctx_printing['sale_device'] = 'CAJA-10' ctx_printing['shop'] = 'Shop Wall Boulevard' ctx_printing['street'] = 'Cll 21 # 172-81. Central Park' ctx_printing['user'] = 'Charles Chapplin' ctx_printing['city'] = 'Dallas' ctx_printing['zip'] = '0876' ctx_printing['phone'] = '591 5513 455' ctx_printing['id_number'] = '123456789-0' ctx_printing['tax_regime'] = 'none' receipt = Receipt(ctx_printing) receipt.set_printer(example_dev) receipt.test_printer()