From e95ea35caa73c77bfdc70138190a3a9c4ed95100 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20van=20der=20Essen?= Date: Tue, 24 Dec 2013 16:11:12 +0100 Subject: [PATCH] [IMP] hw_escpos: company logo on receipt and multi-lingual receipt support bzr revid: fva@openerp.com-20131224151112-3svn5wm1wyf71wim --- addons/hw_escpos/__init__.py | 1 + addons/hw_escpos/controllers/main.py | 38 ++- addons/hw_escpos/escpos/__init__.py | 1 + addons/hw_escpos/escpos/constants.py | 71 ++++ addons/hw_escpos/escpos/escpos.py | 321 +++++++++++++++++++ addons/hw_escpos/escpos/exceptions.py | 80 +++++ addons/hw_escpos/escpos/printer.py | 136 ++++++++ addons/point_of_sale/point_of_sale.py | 12 +- addons/point_of_sale/static/src/js/models.js | 31 ++ 9 files changed, 678 insertions(+), 13 deletions(-) create mode 100644 addons/hw_escpos/escpos/__init__.py create mode 100644 addons/hw_escpos/escpos/constants.py create mode 100644 addons/hw_escpos/escpos/escpos.py create mode 100644 addons/hw_escpos/escpos/exceptions.py create mode 100644 addons/hw_escpos/escpos/printer.py diff --git a/addons/hw_escpos/__init__.py b/addons/hw_escpos/__init__.py index a208bc1c551..e26a47cc12b 100644 --- a/addons/hw_escpos/__init__.py +++ b/addons/hw_escpos/__init__.py @@ -20,6 +20,7 @@ ############################################################################## import controllers +import escpos # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/hw_escpos/controllers/main.py b/addons/hw_escpos/controllers/main.py index ad343a511b5..00d8f7a2195 100644 --- a/addons/hw_escpos/controllers/main.py +++ b/addons/hw_escpos/controllers/main.py @@ -2,14 +2,19 @@ import logging import simplejson import os +import io +import base64 import openerp import time import random import openerp.addons.hw_proxy.controllers.main as hw_proxy import subprocess import usb.core -import escpos -import escpos.printer +from .. import escpos +from ..escpos import printer +#import escpos +#import escpos.printer +from PIL import Image from openerp import http from openerp.http import request @@ -54,20 +59,39 @@ class EscposDriver(hw_proxy.Proxy): return ' ' * indent + left + right + '\n' + logo = None + + + if receipt['company']['logo']: + img = receipt['company']['logo'] + img = img[img.find(',')+1:] + f = io.BytesIO('img') + f.write(base64.decodestring(img)) + f.seek(0) + logo_rgba = Image.open(f) + logo = Image.new('RGB', logo_rgba.size, (255,255,255)) + logo.paste(logo_rgba, mask=logo_rgba.split()[3]) + width = 300 + wfac = width/float(logo_rgba.size[0]) + height = int(logo_rgba.size[1]*wfac) + logo = logo.resize((width,height), Image.ANTIALIAS) if len(printers) > 0: printer = printers[0] eprint = escpos.printer.Usb(printer['vendor'], printer['product']) - #eprint.image(os.path.join(os.path.dirname(__file__),'logo_grayscale.png')) # Receipt Header - eprint.set(align='center',type='b',height=2,width=2) - eprint.text(receipt['shop']['name'] + '\n') + if logo: + eprint._convert_image(logo) + eprint.text('\n') + else: + eprint.set(align='center',type='b',height=2,width=2) + eprint.text(receipt['company']['name'] + '\n') eprint.set(align='center',type='b') - if check(receipt['company']['name']): - eprint.text(receipt['company']['name'] + '\n') + if check(receipt['shop']['name']): + eprint.text(receipt['shop']['name'] + '\n') if check(receipt['company']['contact_address']): eprint.text(receipt['company']['contact address'] + '\n') if check(receipt['company']['phone']): diff --git a/addons/hw_escpos/escpos/__init__.py b/addons/hw_escpos/escpos/__init__.py new file mode 100644 index 00000000000..22a5af61029 --- /dev/null +++ b/addons/hw_escpos/escpos/__init__.py @@ -0,0 +1 @@ +__all__ = ["constants","escpos","exceptions","printer"] diff --git a/addons/hw_escpos/escpos/constants.py b/addons/hw_escpos/escpos/constants.py new file mode 100644 index 00000000000..0be873f8284 --- /dev/null +++ b/addons/hw_escpos/escpos/constants.py @@ -0,0 +1,71 @@ +""" ESC/POS Commands (Constants) """ + +# Feed control sequences +CTL_LF = '\x0a' # Print and line feed +CTL_FF = '\x0c' # Form feed +CTL_CR = '\x0d' # Carriage return +CTL_HT = '\x09' # Horizontal tab +CTL_VT = '\x0b' # Vertical tab +# Printer hardware +HW_INIT = '\x1b\x40' # Clear data in buffer and reset modes +HW_SELECT = '\x1b\x3d\x01' # Printer select +HW_RESET = '\x1b\x3f\x0a\x00' # Reset printer hardware +# Cash Drawer +CD_KICK_2 = '\x1b\x70\x00' # Sends a pulse to pin 2 [] +CD_KICK_5 = '\x1b\x70\x01' # Sends a pulse to pin 5 [] +# Paper +PAPER_FULL_CUT = '\x1d\x56\x00' # Full cut paper +PAPER_PART_CUT = '\x1d\x56\x01' # Partial cut paper +# Text format +TXT_NORMAL = '\x1b\x21\x00' # Normal text +TXT_2HEIGHT = '\x1b\x21\x10' # Double height text +TXT_2WIDTH = '\x1b\x21\x20' # Double width text +TXT_UNDERL_OFF = '\x1b\x2d\x00' # Underline font OFF +TXT_UNDERL_ON = '\x1b\x2d\x01' # Underline font 1-dot ON +TXT_UNDERL2_ON = '\x1b\x2d\x02' # Underline font 2-dot ON +TXT_BOLD_OFF = '\x1b\x45\x00' # Bold font OFF +TXT_BOLD_ON = '\x1b\x45\x01' # Bold font ON +TXT_FONT_A = '\x1b\x4d\x00' # Font type A +TXT_FONT_B = '\x1b\x4d\x01' # Font type B +TXT_ALIGN_LT = '\x1b\x61\x00' # Left justification +TXT_ALIGN_CT = '\x1b\x61\x01' # Centering +TXT_ALIGN_RT = '\x1b\x61\x02' # Right justification +# Text Encoding + +TXT_ENC_PC437 = '\x1b\x74\x00' # PC437 USA +TXT_ENC_KATAKANA= '\x1b\x74\x01' # KATAKANA (JAPAN) +TXT_ENC_PC850 = '\x1b\x74\x02' # PC850 Multilingual +TXT_ENC_PC860 = '\x1b\x74\x03' # PC860 Portuguese +TXT_ENC_PC863 = '\x1b\x74\x04' # PC863 Canadian-French +TXT_ENC_PC865 = '\x1b\x74\x05' # PC865 Nordic +TXT_ENC_KANJI6 = '\x1b\x74\x06' # One-pass Kanji, Hiragana +TXT_ENC_KANJI7 = '\x1b\x74\x07' # One-pass Kanji +TXT_ENC_KANJI8 = '\x1b\x74\x08' # One-pass Kanji +TXT_ENC_WPC1252 = '\x1b\x74\x10' # WPC1252 +TXT_ENC_PC866 = '\x1b\x74\x11' # PC866 Cyrillic #2 +TXT_ENC_PC852 = '\x1b\x74\x12' # PC852 Latin2 +TXT_ENC_PC858 = '\x1b\x74\x13' # PC858 Euro + + + +# Barcode format +BARCODE_TXT_OFF = '\x1d\x48\x00' # HRI barcode chars OFF +BARCODE_TXT_ABV = '\x1d\x48\x01' # HRI barcode chars above +BARCODE_TXT_BLW = '\x1d\x48\x02' # HRI barcode chars below +BARCODE_TXT_BTH = '\x1d\x48\x03' # HRI barcode chars both above and below +BARCODE_FONT_A = '\x1d\x66\x00' # Font type A for HRI barcode chars +BARCODE_FONT_B = '\x1d\x66\x01' # Font type B for HRI barcode chars +BARCODE_HEIGHT = '\x1d\x68\x64' # Barcode Height [1-255] +BARCODE_WIDTH = '\x1d\x77\x03' # Barcode Width [2-6] +BARCODE_UPC_A = '\x1d\x6b\x00' # Barcode type UPC-A +BARCODE_UPC_E = '\x1d\x6b\x01' # Barcode type UPC-E +BARCODE_EAN13 = '\x1d\x6b\x02' # Barcode type EAN13 +BARCODE_EAN8 = '\x1d\x6b\x03' # Barcode type EAN8 +BARCODE_CODE39 = '\x1d\x6b\x04' # Barcode type CODE39 +BARCODE_ITF = '\x1d\x6b\x05' # Barcode type ITF +BARCODE_NW7 = '\x1d\x6b\x06' # Barcode type NW7 +# Image format +S_RASTER_N = '\x1d\x76\x30\x00' # Set raster image normal size +S_RASTER_2W = '\x1d\x76\x30\x01' # Set raster image double width +S_RASTER_2H = '\x1d\x76\x30\x02' # Set raster image double height +S_RASTER_Q = '\x1d\x76\x30\x03' # Set raster image quadruple diff --git a/addons/hw_escpos/escpos/escpos.py b/addons/hw_escpos/escpos/escpos.py new file mode 100644 index 00000000000..a18faa1487f --- /dev/null +++ b/addons/hw_escpos/escpos/escpos.py @@ -0,0 +1,321 @@ +#!/usr/bin/python +''' +@author: Manuel F Martinez +@organization: Bashlinux +@copyright: Copyright (c) 2012 Bashlinux +@license: GPL +''' + +import Image +import qrcode +import time +import copy + +from constants import * +from exceptions import * + +class Escpos: + """ ESC/POS Printer object """ + device = None + encoding = None + + + def _check_image_size(self, size): + """ Check and fix the size of the image to 32 bits """ + if size % 32 == 0: + return (0, 0) + else: + image_border = 32 - (size % 32) + if (image_border % 2) == 0: + return (image_border / 2, image_border / 2) + else: + return (image_border / 2, (image_border / 2) + 1) + + + def _print_image(self, line, size): + """ Print formatted image """ + i = 0 + cont = 0 + buffer = "" + + self._raw(S_RASTER_N) + buffer = "%02X%02X%02X%02X" % (((size[0]/size[1])/8), 0, size[1], 0) + self._raw(buffer.decode('hex')) + buffer = "" + + while i < len(line): + hex_string = int(line[i:i+8],2) + buffer += "%02X" % hex_string + i += 8 + cont += 1 + if cont % 4 == 0: + self._raw(buffer.decode("hex")) + buffer = "" + cont = 0 + + + def _convert_image(self, im): + """ Parse image and prepare it to a printable format """ + pixels = [] + pix_line = "" + im_left = "" + im_right = "" + switch = 0 + img_size = [ 0, 0 ] + + + if im.size[0] > 512: + print "WARNING: Image is wider than 512 and could be truncated at print time " + if im.size[1] > 255: + raise ImageSizeError() + + im_border = self._check_image_size(im.size[0]) + for i in range(im_border[0]): + im_left += "0" + for i in range(im_border[1]): + im_right += "0" + + for y in range(im.size[1]): + img_size[1] += 1 + pix_line += im_left + img_size[0] += im_border[0] + for x in range(im.size[0]): + img_size[0] += 1 + RGB = im.getpixel((x, y)) + im_color = (RGB[0] + RGB[1] + RGB[2]) + im_pattern = "1X0" + pattern_len = len(im_pattern) + switch = (switch - 1 ) * (-1) + for x in range(pattern_len): + if im_color <= (255 * 3 / pattern_len * (x+1)): + if im_pattern[x] == "X": + pix_line += "%d" % switch + else: + pix_line += im_pattern[x] + break + elif im_color > (255 * 3 / pattern_len * pattern_len) and im_color <= (255 * 3): + pix_line += im_pattern[-1] + break + pix_line += im_right + img_size[0] += im_border[1] + + self._print_image(pix_line, img_size) + + + def image(self,path_img): + """ Open image file """ + im_open = Image.open(path_img) + im = im_open.convert("RGB") + # Convert the RGB image in printable image + self._convert_image(im) + + + def qr(self,text): + """ Print QR Code for the provided string """ + qr_code = qrcode.QRCode(version=4, box_size=4, border=1) + qr_code.add_data(text) + qr_code.make(fit=True) + qr_img = qr_code.make_image() + im = qr_img._img.convert("RGB") + # Convert the RGB image in printable image + self._convert_image(im) + + + def barcode(self, code, bc, width, height, pos, font): + """ Print Barcode """ + # Align Bar Code() + self._raw(TXT_ALIGN_CT) + # Height + if height >=2 or height <=6: + self._raw(BARCODE_HEIGHT) + else: + raise BarcodeSizeError() + # Width + if width >= 1 or width <=255: + self._raw(BARCODE_WIDTH) + else: + raise BarcodeSizeError() + # Font + if font.upper() == "B": + self._raw(BARCODE_FONT_B) + else: # DEFAULT FONT: A + self._raw(BARCODE_FONT_A) + # Position + if pos.upper() == "OFF": + self._raw(BARCODE_TXT_OFF) + elif pos.upper() == "BOTH": + self._raw(BARCODE_TXT_BTH) + elif pos.upper() == "ABOVE": + self._raw(BARCODE_TXT_ABV) + else: # DEFAULT POSITION: BELOW + self._raw(BARCODE_TXT_BLW) + # Type + if bc.upper() == "UPC-A": + self._raw(BARCODE_UPC_A) + elif bc.upper() == "UPC-E": + self._raw(BARCODE_UPC_E) + elif bc.upper() == "EAN13": + self._raw(BARCODE_EAN13) + elif bc.upper() == "EAN8": + self._raw(BARCODE_EAN8) + elif bc.upper() == "CODE39": + self._raw(BARCODE_CODE39) + elif bc.upper() == "ITF": + self._raw(BARCODE_ITF) + elif bc.upper() == "NW7": + self._raw(BARCODE_NW7) + else: + raise BarcodeTypeError() + # Print Code + if code: + self._raw(code) + else: + raise exception.BarcodeCodeError() + + def text(self,txt): + """ Print Utf8 encoded alpha-numeric text """ + try: + txt = txt.decode('utf-8') + except: + try: + txt = txt.decode('utf-16') + except: + pass + + def encode_char(char): + encoded = '' + encoding = self.encoding + encodings = { + 'cp437': TXT_ENC_PC437, + 'cp850': TXT_ENC_PC850, + 'cp852': TXT_ENC_PC852, + 'cp858': TXT_ENC_PC858, + 'cp865': TXT_ENC_PC860, + 'cp863': TXT_ENC_PC863, + 'cp865': TXT_ENC_PC865, + 'cp866': TXT_ENC_PC866, + } + remaining = copy.copy(encodings) + + if not encoding : + encoding = 'cp437' + + while True: + try: + encoded = char.encode(encoding) + break + except ValueError: + if encoding in remaining: + del remaining[encoding] + if len(remaining) >= 1: + encoding = remaining.items()[0][0] + else: + print 'COULD NOT ENCODE:',char, char.__repr__() + encoded = '\xA8' # could not encode, output error character + break; + + if encoding != self.encoding: + self.encoding = encoding + encoded = encodings[encoding] + encoded + + return encoded + + buffer = '' + for char in txt: + buffer += encode_char(char) + + if buffer: + self._raw(buffer) + + def set(self, align='left', font='a', type='normal', width=1, height=1): + """ Set text properties """ + # Align + if align.upper() == "CENTER": + self._raw(TXT_ALIGN_CT) + elif align.upper() == "RIGHT": + self._raw(TXT_ALIGN_RT) + elif align.upper() == "LEFT": + self._raw(TXT_ALIGN_LT) + # Font + if font.upper() == "B": + self._raw(TXT_FONT_B) + else: # DEFAULT FONT: A + self._raw(TXT_FONT_A) + # Type + if type.upper() == "B": + self._raw(TXT_BOLD_ON) + self._raw(TXT_UNDERL_OFF) + elif type.upper() == "U": + self._raw(TXT_BOLD_OFF) + self._raw(TXT_UNDERL_ON) + elif type.upper() == "U2": + self._raw(TXT_BOLD_OFF) + self._raw(TXT_UNDERL2_ON) + elif type.upper() == "BU": + self._raw(TXT_BOLD_ON) + self._raw(TXT_UNDERL_ON) + elif type.upper() == "BU2": + self._raw(TXT_BOLD_ON) + self._raw(TXT_UNDERL2_ON) + elif type.upper == "NORMAL": + self._raw(TXT_BOLD_OFF) + self._raw(TXT_UNDERL_OFF) + # Width + if width == 2 and height != 2: + self._raw(TXT_NORMAL) + self._raw(TXT_2WIDTH) + elif height == 2 and width != 2: + self._raw(TXT_NORMAL) + self._raw(TXT_2HEIGHT) + elif height == 2 and width == 2: + self._raw(TXT_2WIDTH) + self._raw(TXT_2HEIGHT) + else: # DEFAULT SIZE: NORMAL + self._raw(TXT_NORMAL) + + + def cut(self, mode=''): + """ Cut paper """ + # Fix the size between last line and cut + # TODO: handle this with a line feed + self._raw("\n\n\n\n\n\n") + if mode.upper() == "PART": + self._raw(PAPER_PART_CUT) + else: # DEFAULT MODE: FULL CUT + self._raw(PAPER_FULL_CUT) + + + def cashdraw(self, pin): + """ Send pulse to kick the cash drawer """ + if pin == 2: + self._raw(CD_KICK_2) + elif pin == 5: + self._raw(CD_KICK_5) + else: + raise CashDrawerError() + + + def hw(self, hw): + """ Hardware operations """ + if hw.upper() == "INIT": + self._raw(HW_INIT) + elif hw.upper() == "SELECT": + self._raw(HW_SELECT) + elif hw.upper() == "RESET": + self._raw(HW_RESET) + else: # DEFAULT: DOES NOTHING + pass + + + def control(self, ctl): + """ Feed control sequences """ + if ctl.upper() == "LF": + self._raw(CTL_LF) + elif ctl.upper() == "FF": + self._raw(CTL_FF) + elif ctl.upper() == "CR": + self._raw(CTL_CR) + elif ctl.upper() == "HT": + self._raw(CTL_HT) + elif ctl.upper() == "VT": + self._raw(CTL_VT) diff --git a/addons/hw_escpos/escpos/exceptions.py b/addons/hw_escpos/escpos/exceptions.py new file mode 100644 index 00000000000..adbe6484f3e --- /dev/null +++ b/addons/hw_escpos/escpos/exceptions.py @@ -0,0 +1,80 @@ +""" ESC/POS Exceptions classes """ + +import os + +class Error(Exception): + """ Base class for ESC/POS errors """ + def __init__(self, msg, status=None): + Exception.__init__(self) + self.msg = msg + self.resultcode = 1 + if status is not None: + self.resultcode = status + + def __str__(self): + return self.msg + +# Result/Exit codes +# 0 = success +# 10 = No Barcode type defined +# 20 = Barcode size values are out of range +# 30 = Barcode text not supplied +# 40 = Image height is too large +# 50 = No string supplied to be printed +# 60 = Invalid pin to send Cash Drawer pulse + + +class BarcodeTypeError(Error): + def __init__(self, msg=""): + Error.__init__(self, msg) + self.msg = msg + self.resultcode = 10 + + def __str__(self): + return "No Barcode type is defined" + +class BarcodeSizeError(Error): + def __init__(self, msg=""): + Error.__init__(self, msg) + self.msg = msg + self.resultcode = 20 + + def __str__(self): + return "Barcode size is out of range" + +class BarcodeCodeError(Error): + def __init__(self, msg=""): + Error.__init__(self, msg) + self.msg = msg + self.resultcode = 30 + + def __str__(self): + return "Code was not supplied" + +class ImageSizeError(Error): + def __init__(self, msg=""): + Error.__init__(self, msg) + self.msg = msg + self.resultcode = 40 + + def __str__(self): + return "Image height is longer than 255px and can't be printed" + +class TextError(Error): + def __init__(self, msg=""): + Error.__init__(self, msg) + self.msg = msg + self.resultcode = 50 + + def __str__(self): + return "Text string must be supplied to the text() method" + + +class CashDrawerError(Error): + def __init__(self, msg=""): + Error.__init__(self, msg) + self.msg = msg + self.resultcode = 60 + + def __str__(self): + return "Valid pin must be set to send pulse" diff --git a/addons/hw_escpos/escpos/printer.py b/addons/hw_escpos/escpos/printer.py new file mode 100644 index 00000000000..dd5aa93e361 --- /dev/null +++ b/addons/hw_escpos/escpos/printer.py @@ -0,0 +1,136 @@ +#!/usr/bin/python +''' +@author: Manuel F Martinez +@organization: Bashlinux +@copyright: Copyright (c) 2012 Bashlinux +@license: GPL +''' + +import usb.core +import usb.util +import serial +import socket + +from escpos import * +from constants import * +from exceptions import * + +class Usb(Escpos): + """ Define USB printer """ + + def __init__(self, idVendor, idProduct, interface=0, in_ep=0x82, out_ep=0x01): + """ + @param idVendor : Vendor ID + @param idProduct : Product ID + @param interface : USB device interface + @param in_ep : Input end point + @param out_ep : Output end point + """ + self.idVendor = idVendor + self.idProduct = idProduct + self.interface = interface + self.in_ep = in_ep + self.out_ep = out_ep + self.open() + + + def open(self): + """ Search device on USB tree and set is as escpos device """ + self.device = usb.core.find(idVendor=self.idVendor, idProduct=self.idProduct) + if self.device is None: + print "Cable isn't plugged in" + + if self.device.is_kernel_driver_active(0): + try: + self.device.detach_kernel_driver(0) + except usb.core.USBError as e: + print "Could not detatch kernel driver: %s" % str(e) + + try: + self.device.set_configuration() + self.device.reset() + except usb.core.USBError as e: + print "Could not set configuration: %s" % str(e) + + + def _raw(self, msg): + """ Print any command sent in raw format """ + self.device.write(self.out_ep, msg, self.interface) + + + def __del__(self): + """ Release USB interface """ + if self.device: + usb.util.dispose_resources(self.device) + self.device = None + + + +class Serial(Escpos): + """ Define Serial printer """ + + def __init__(self, devfile="/dev/ttyS0", baudrate=9600, bytesize=8, timeout=1): + """ + @param devfile : Device file under dev filesystem + @param baudrate : Baud rate for serial transmission + @param bytesize : Serial buffer size + @param timeout : Read/Write timeout + """ + self.devfile = devfile + self.baudrate = baudrate + self.bytesize = bytesize + self.timeout = timeout + self.open() + + + def open(self): + """ Setup serial port and set is as escpos device """ + self.device = serial.Serial(port=self.devfile, baudrate=self.baudrate, bytesize=self.bytesize, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=self.timeout, dsrdtr=True) + + if self.device is not None: + print "Serial printer enabled" + else: + print "Unable to open serial printer on: %s" % self.devfile + + + def _raw(self, msg): + """ Print any command sent in raw format """ + self.device.write(msg) + + + def __del__(self): + """ Close Serial interface """ + if self.device is not None: + self.device.close() + + + +class Network(Escpos): + """ Define Network printer """ + + def __init__(self,host,port=9100): + """ + @param host : Printer's hostname or IP address + @param port : Port to write to + """ + self.host = host + self.port = port + self.open() + + + def open(self): + """ Open TCP socket and set it as escpos device """ + self.device = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + self.device.connect((self.host, self.port)) + + if self.device is None: + print "Could not open socket for %s" % self.host + + + def _raw(self, msg): + self.device.send(msg) + + + def __del__(self): + """ Close TCP connection """ + self.device.close() diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index 638c1fce4d2..e34207cbdc3 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -620,10 +620,10 @@ class pos_order(osv.osv): 'warehouse_id': fields.related('session_id', 'config_id', 'warehouse_id', relation='stock.warehouse', type='many2one', string='Warehouse', store=True, readonly=True), 'date_order': fields.datetime('Order Date', readonly=True, select=True), 'user_id': fields.many2one('res.users', 'Salesman', help="Person who uses the the cash register. It can be a reliever, a student or an interim employee."), - 'amount_tax': fields.function(_amount_all, string='Taxes', digits_compute=dp.get_precision('Point Of Sale'), multi='all'), + 'amount_tax': fields.function(_amount_all, string='Taxes', digits_compute=dp.get_precision('Account'), multi='all'), 'amount_total': fields.function(_amount_all, string='Total', multi='all'), - 'amount_paid': fields.function(_amount_all, string='Paid', states={'draft': [('readonly', False)]}, readonly=True, digits_compute=dp.get_precision('Point Of Sale'), multi='all'), - 'amount_return': fields.function(_amount_all, 'Returned', digits_compute=dp.get_precision('Point Of Sale'), multi='all'), + 'amount_paid': fields.function(_amount_all, string='Paid', states={'draft': [('readonly', False)]}, readonly=True, digits_compute=dp.get_precision('Account'), multi='all'), + 'amount_return': fields.function(_amount_all, 'Returned', digits_compute=dp.get_precision('Account'), multi='all'), 'lines': fields.one2many('pos.order.line', 'order_id', 'Order Lines', states={'draft': [('readonly', False)]}, readonly=True), 'statement_ids': fields.one2many('account.bank.statement.line', 'pos_statement_id', 'Payments', states={'draft': [('readonly', False)]}, readonly=True), 'pricelist_id': fields.many2one('product.pricelist', 'Pricelist', required=True, states={'draft': [('readonly', False)]}, readonly=True), @@ -1226,11 +1226,11 @@ class pos_order_line(osv.osv): 'name': fields.char('Line No', size=32, required=True), 'notice': fields.char('Discount Notice', size=128), 'product_id': fields.many2one('product.product', 'Product', domain=[('sale_ok', '=', True)], required=True, change_default=True), - 'price_unit': fields.float(string='Unit Price', digits=(16, 2)), - 'qty': fields.float('Quantity', digits=(16, 2)), + 'price_unit': fields.float(string='Unit Price', digits_compute=dp.get_precision('Account')), + 'qty': fields.float('Quantity', digits_compute=dp.get_precision('Product UoS')), 'price_subtotal': fields.function(_amount_line_all, multi='pos_order_line_amount', string='Subtotal w/o Tax', store=True), 'price_subtotal_incl': fields.function(_amount_line_all, multi='pos_order_line_amount', string='Subtotal', store=True), - 'discount': fields.float('Discount (%)', digits=(16, 2)), + 'discount': fields.float('Discount (%)', digits_compute=dp.get_precision('Account')), 'order_id': fields.many2one('pos.order', 'Order Ref', ondelete='cascade'), 'create_date': fields.datetime('Creation Date', readonly=True), } diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index f8ddc7cb056..97475215469 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -28,6 +28,9 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.debug = jQuery.deparam(jQuery.param.querystring()).debug !== undefined; //debug mode // Business data; loaded from the server at launch + this.accounting_precision = 2; //TODO + this.company_logo = null; + this.company_logo_base64 = ''; this.currency = null; this.shop = null; this.company = null; @@ -153,6 +156,12 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }).then(function(currencies){ self.currency = currencies[0]; + /* + return (new instance.web.Model('decimal.precision')).call('get_precision',[['Account']]); + }).then(function(precision){ + self.accounting_precision = precision; + console.log("PRECISION",precision); +*/ return self.fetch('product.packaging',['ean','product_id']); }).then(function(packagings){ self.db.add_packagings(packagings); @@ -197,6 +206,27 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal } } self.cashregisters = bankstatements; + + // Load the company Logo + + self.company_logo = new Image(); + self.company_logo.crossOrigin = 'anonymous'; + var logo_loaded = new $.Deferred(); + self.company_logo.onload = function(){ + var c = document.createElement('canvas'); + c.width = self.company_logo.width; + c.height = self.company_logo.height; + var ctx = c.getContext('2d'); + ctx.drawImage(self.company_logo,0,0); + self.company_logo_base64 = c.toDataURL(); + logo_loaded.resolve(); + }; + self.company_logo.onerror = function(){ + logo_loaded.reject(); + }; + self.company_logo.src = window.location.origin + '/web/binary/company_logo'; + + return logo_loaded; }); return loaded; @@ -856,6 +886,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal vat: company.vat, name: company.name, phone: company.phone, + logo: this.pos.company_logo_base64, }, shop:{ name: shop.name,