diff --git a/addons/hw_escpos/controllers/main.py b/addons/hw_escpos/controllers/main.py index e950950a985..9e5656ef986 100644 --- a/addons/hw_escpos/controllers/main.py +++ b/addons/hw_escpos/controllers/main.py @@ -12,6 +12,7 @@ import math import md5 import openerp.addons.hw_proxy.controllers.main as hw_proxy import subprocess +import traceback from threading import Thread, Lock from Queue import Queue, Empty @@ -36,6 +37,7 @@ from openerp.tools.translate import _ _logger = logging.getLogger(__name__) + class EscposDriver(Thread): def __init__(self): Thread.__init__(self) @@ -45,6 +47,7 @@ class EscposDriver(Thread): def connected_usb_devices(self): connected = [] + for device in supported_devices.device_list: if usb.core.find(idVendor=device['vendor'], idProduct=device['product']) != None: connected.append(device) @@ -74,6 +77,8 @@ class EscposDriver(Thread): self.push_task('status') return self.status + + def open_cashbox(self,printer): printer.cashdraw(2) printer.cashdraw(5) @@ -113,6 +118,9 @@ class EscposDriver(Thread): if timestamp >= time.time() - 1 * 60 * 60: self.print_receipt_body(printer,data) printer.cut() + elif task == 'xml_receipt': + if timestamp >= time.time() - 1 * 60 * 60: + printer.receipt(data) elif task == 'cashbox': if timestamp >= time.time() - 12: self.open_cashbox(printer) @@ -123,7 +131,8 @@ class EscposDriver(Thread): except Exception as e: self.set_status('error', str(e)) - _logger.error(e); + errmsg = str(e) + '\n' + '-'*60+'\n' + traceback.format_exc() + '-'*60 + '\n' + _logger.error(errmsg); def push_task(self,task, data = None): self.lockedstart() @@ -142,12 +151,15 @@ class EscposDriver(Thread): if len(ips) == 0: eprint.text('ERROR: Could not connect to LAN\n\nPlease check that the PosBox is correc-\ntly connected with a network cable,\n that the LAN is setup with DHCP, and\nthat network addresses are available') elif len(ips) == 1: - eprint.text('IP Address\n'+ips[0]+'\n') + eprint.text('IP Address:\n'+ips[0]+'\n') else: - eprint.text('IP Addresses\n') + eprint.text('IP Addresses:\n') for ip in ips: eprint.text(ip+'\n') + if len(ips) >= 1: + eprint.text('\nHomepage:\nhttp://'+ips[0]+':8069\n') + eprint.text('\n\n') eprint.cut() @@ -278,10 +290,10 @@ class EscposDriver(Thread): driver = EscposDriver() +driver.push_task('printstatus') + hw_proxy.drivers['escpos'] = driver -driver.push_task('printstatus') - class EscposProxy(hw_proxy.Proxy): @http.route('/hw_proxy/open_cashbox', type='json', auth='none', cors='*') @@ -293,4 +305,9 @@ class EscposProxy(hw_proxy.Proxy): def print_receipt(self, receipt): _logger.info('ESC/POS: PRINT RECEIPT') driver.push_task('receipt',receipt) + + @http.route('/hw_proxy/print_xml_receipt', type='json', auth='none', cors='*') + def print_receipt(self, receipt): + _logger.info('ESC/POS: PRINT XML RECEIPT') + driver.push_task('xml_receipt',receipt) diff --git a/addons/hw_escpos/escpos/constants.py b/addons/hw_escpos/escpos/constants.py index 723c67013f0..6f0be0bb135 100644 --- a/addons/hw_escpos/escpos/constants.py +++ b/addons/hw_escpos/escpos/constants.py @@ -22,6 +22,7 @@ PAPER_PART_CUT = '\x1d\x56\x01' # Partial cut paper TXT_NORMAL = '\x1b\x21\x00' # Normal text TXT_2HEIGHT = '\x1b\x21\x10' # Double height text TXT_2WIDTH = '\x1b\x21\x20' # Double width text +TXT_DOUBLE = '\x1b\x21\x30' # Double height & Width 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 diff --git a/addons/hw_escpos/escpos/escpos.py b/addons/hw_escpos/escpos/escpos.py index a4942019f8c..84b52f99d8c 100644 --- a/addons/hw_escpos/escpos/escpos.py +++ b/addons/hw_escpos/escpos/escpos.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +# -*- coding: utf-8 -*- ''' @author: Manuel F Martinez @organization: Bashlinux @@ -17,6 +17,10 @@ import io import base64 import math import md5 +import re +import traceback +import xml.etree.ElementTree as ET +import xml.dom.minidom as minidom from PIL import Image @@ -30,13 +34,272 @@ except ImportError: from constants import * from exceptions import * +def utfstr(stuff): + """ converts stuff to string and does without failing if stuff is a utf8 string """ + if isinstance(stuff,basestring): + return stuff + else: + return str(stuff) + +class StyleStack: + """ + The stylestack is used by the xml receipt serializer to compute the active styles along the xml + document. Styles are just xml attributes, there is no css mechanism. But the style applied by + the attributes are inherited by deeper nodes. + """ + def __init__(self): + self.stack = [] + self.defaults = { # default style values + 'align': 'left', + 'underline': 'off', + 'bold': 'off', + 'size': 'normal', + 'font' : 'a', + 'width': 48, + 'indent': 0, + 'tabwidth': 2, + 'bullet': ' - ', + 'line-ratio':0.5, + + 'value-decimals': 2, + 'value-symbol': '', + 'value-symbol-position': 'after', + 'value-autoint': 'off', + 'value-decimals-separator': '.', + 'value-thousands-separator': ',', + 'value-width': 0, + + } + + self.types = { # attribute types, default is string and can be ommitted + 'width': 'int', + 'indent': 'int', + 'tabwidth': 'int', + 'line-ratio': 'float', + 'value-decimals': 'int', + 'value-width': 'int', + } + + self.cmds = { + # translation from styles to escpos commands + # some style do not correspond to escpos command are used by + # the serializer instead + 'align': { + 'left': TXT_ALIGN_LT, + 'right': TXT_ALIGN_RT, + 'center': TXT_ALIGN_CT, + }, + 'underline': { + 'off': TXT_UNDERL_OFF, + 'on': TXT_UNDERL_ON, + 'double': TXT_UNDERL2_ON, + }, + 'bold': { + 'off': TXT_BOLD_OFF, + 'on': TXT_BOLD_ON, + }, + 'font': { + 'a': TXT_FONT_A, + 'b': TXT_FONT_B, + }, + 'size': { + 'normal': TXT_NORMAL, + 'double-height': TXT_2HEIGHT, + 'double-width': TXT_2WIDTH, + 'double': TXT_DOUBLE, + } + } + + self.push(self.defaults) + + def get(self,style): + """ what's the value of a style at the current stack level""" + level = len(self.stack) -1 + while level >= 0: + if style in self.stack[level]: + return self.stack[level][style] + else: + level = level - 1 + return None + + def enforce_type(self, attr, val): + """converts a value to the attribute's type""" + if not attr in self.types: + return utfstr(val) + elif self.types[attr] == 'int': + return int(float(val)) + elif self.types[attr] == 'float': + return float(val) + else: + return utfstr(val) + + def push(self, style={}): + """push a new level on the stack with a style dictionnary containing style:value pairs""" + _style = {} + for attr in style: + if attr in self.cmds and not style[attr] in self.cmds[attr]: + print 'WARNING: ESC/POS PRINTING: ignoring invalid value: '+utfstr(style[attr])+' for style: '+utfstr(attr) + else: + _style[attr] = self.enforce_type(attr, style[attr]) + self.stack.append(_style) + + def set(self, style={}): + """overrides style values at the current stack level""" + _style = {} + for attr in style: + if attr in self.cmds and not style[attr] in self.cmds[attr]: + print 'WARNING: ESC/POS PRINTING: ignoring invalid value: '+utfstr(style[attr])+' for style: '+utfstr(attr) + else: + self.stack[-1][attr] = self.enforce_type(attr, style[attr]) + + def pop(self): + """ pop a style stack level """ + if len(self.stack) > 1 : + self.stack = self.stack[:-1] + + def to_escpos(self): + """ converts the current style to an escpos command string """ + cmd = '' + for style in self.cmds: + cmd += self.cmds[style][self.get(style)] + return cmd + +class XmlSerializer: + """ + Converts the xml inline / block tree structure to a string, + keeping track of newlines and spacings. + The string is outputted asap to the provided escpos driver. + """ + def __init__(self,escpos): + self.escpos = escpos + self.stack = ['block'] + self.dirty = False + + def start_inline(self,stylestack=None): + """ starts an inline entity with an optional style definition """ + self.stack.append('inline') + if self.dirty: + self.escpos._raw(' ') + if stylestack: + self.style(stylestack) + + def start_block(self,stylestack=None): + """ starts a block entity with an optional style definition """ + if self.dirty: + self.escpos._raw('\n') + self.dirty = False + self.stack.append('block') + if stylestack: + self.style(stylestack) + + def end_entity(self): + """ ends the entity definition. (but does not cancel the active style!) """ + if self.stack[-1] == 'block' and self.dirty: + self.escpos._raw('\n') + self.dirty = False + if len(self.stack) > 1: + self.stack = self.stack[:-1] + + def pre(self,text): + """ puts a string of text in the entity keeping the whitespace intact """ + if text: + self.escpos.text(text) + self.dirty = True + + def text(self,text): + """ puts text in the entity. Whitespace and newlines are stripped to single spaces. """ + if text: + text = utfstr(text) + text = text.strip() + text = re.sub('\s+',' ',text) + if text: + self.dirty = True + self.escpos.text(text) + + def linebreak(self): + """ inserts a linebreak in the entity """ + self.dirty = False + self.escpos._raw('\n') + + def style(self,stylestack): + """ apply a style to the entity (only applies to content added after the definition) """ + self.raw(stylestack.to_escpos()) + + def raw(self,raw): + """ puts raw text or escpos command in the entity without affecting the state of the serializer """ + self.escpos._raw(raw) + +class XmlLineSerializer: + """ + This is used to convert a xml tree into a single line, with a left and a right part. + The content is not output to escpos directly, and is intended to be fedback to the + XmlSerializer as the content of a block entity. + """ + def __init__(self, indent=0, tabwidth=2, width=48, ratio=0.5): + self.tabwidth = tabwidth + self.indent = indent + self.width = max(0, width - int(tabwidth*indent)) + self.lwidth = int(self.width*ratio) + self.rwidth = max(0, self.width - self.lwidth) + self.clwidth = 0 + self.crwidth = 0 + self.lbuffer = '' + self.rbuffer = '' + self.left = True + + def _txt(self,txt): + if self.left: + if self.clwidth < self.lwidth: + txt = txt[:max(0, self.lwidth - self.clwidth)] + self.lbuffer += txt + self.clwidth += len(txt) + else: + if self.crwidth < self.rwidth: + txt = txt[:max(0, self.rwidth - self.crwidth)] + self.rbuffer += txt + self.crwidth += len(txt) + + def start_inline(self,stylestack=None): + if (self.left and self.clwidth) or (not self.left and self.crwidth): + self._txt(' ') + + def start_block(self,stylestack=None): + self.start_inline(stylestack) + + def end_entity(self): + pass + + def pre(self,text): + if text: + self._txt(text) + def text(self,text): + if text: + text = utfstr(text) + text = text.strip() + text = re.sub('\s+',' ',text) + if text: + self._txt(text) + + def linebreak(self): + pass + def style(self,stylestack): + pass + def raw(self,raw): + pass + + def start_right(self): + self.left = False + + def get_line(self): + return ' ' * self.indent * self.tabwidth + self.lbuffer + ' ' * (self.width - self.clwidth - self.crwidth) + self.rbuffer + + class Escpos: """ ESC/POS Printer object """ device = None encoding = None img_cache = {} - def _check_image_size(self, size): """ Check and fix the size of the image to 32 bits """ if size % 32 == 0: @@ -48,7 +311,6 @@ class Escpos: else: return (image_border / 2, (image_border / 2) + 1) - def _print_image(self, line, size): """ Print formatted image """ i = 0 @@ -101,7 +363,6 @@ class Escpos: return raw - def _convert_image(self, im): """ Parse image and prepare it to a printable format """ pixels = [] @@ -197,8 +458,7 @@ class Escpos: # Convert the RGB image in printable image self._convert_image(im) - - def barcode(self, code, bc, width, height, pos, font): + def barcode(self, code, bc, width=255, height=2, pos='below', font='a'): """ Print Barcode """ # Align Bar Code() self._raw(TXT_ALIGN_CT) @@ -249,6 +509,193 @@ class Escpos: else: raise exception.BarcodeCodeError() + def receipt(self,xml): + """ + Prints an xml based receipt definition + """ + + def strclean(string): + if not string: + string = '' + string = string.strip() + string = re.sub('\s+',' ',string) + return string + + def format_value(value, decimals=3, width=0, decimals_separator='.', thousands_separator=',', autoint=False, symbol='', position='after'): + decimals = max(0,int(decimals)) + width = max(0,int(width)) + value = float(value) + + if autoint and math.floor(value) == value: + decimals = 0 + if width == 0: + width = '' + + if thousands_separator: + formatstr = "{:"+str(width)+",."+str(decimals)+"f}" + else: + formatstr = "{:"+str(width)+"."+str(decimals)+"f}" + + + ret = formatstr.format(value) + ret = ret.replace(',','COMMA') + ret = ret.replace('.','DOT') + ret = ret.replace('COMMA',thousands_separator) + ret = ret.replace('DOT',decimals_separator) + + if symbol: + if position == 'after': + ret = ret + symbol + else: + ret = symbol + ret + return ret + + def print_elem(stylestack, serializer, elem, indent=0): + + elem_styles = { + 'h1': {'bold': 'on', 'size':'double'}, + 'h2': {'size':'double'}, + 'h3': {'bold': 'on', 'size':'double-height'}, + 'h4': {'size': 'double-height'}, + 'h5': {'bold': 'on'}, + 'em': {'font': 'b'}, + 'b': {'bold': 'on'}, + } + + stylestack.push() + if elem.tag in elem_styles: + stylestack.set(elem_styles[elem.tag]) + stylestack.set(elem.attrib) + + if elem.tag in ('p','div','section','article','receipt','header','footer','li','h1','h2','h3','h4','h5'): + serializer.start_block(stylestack) + serializer.text(elem.text) + for child in elem: + print_elem(stylestack,serializer,child) + serializer.start_inline(stylestack) + serializer.text(child.tail) + serializer.end_entity() + serializer.end_entity() + + elif elem.tag in ('span','em','b','left','right'): + serializer.start_inline(stylestack) + serializer.text(elem.text) + for child in elem: + print_elem(stylestack,serializer,child) + serializer.start_inline(stylestack) + serializer.text(child.tail) + serializer.end_entity() + serializer.end_entity() + + elif elem.tag == 'value': + serializer.start_inline(stylestack) + serializer.pre(format_value( + elem.text, + decimals=stylestack.get('value-decimals'), + width=stylestack.get('value-width'), + decimals_separator=stylestack.get('value-decimals-separator'), + thousands_separator=stylestack.get('value-thousands-separator'), + autoint=(stylestack.get('autoint') == 'on'), + symbol=stylestack.get('value-symbol'), + position=stylestack.get('value-symbol-position') + )) + serializer.end_entity() + + elif elem.tag == 'line': + width = stylestack.get('width') + if stylestack.get('size') in ('double', 'double-width'): + width = width / 2 + + lineserializer = XmlLineSerializer(stylestack.get('indent')+indent,stylestack.get('tabwidth'),width,stylestack.get('line-ratio')) + serializer.start_block(stylestack) + for child in elem: + if child.tag == 'left': + print_elem(stylestack,lineserializer,child,indent=indent) + elif child.tag == 'right': + lineserializer.start_right() + print_elem(stylestack,lineserializer,child,indent=indent) + serializer.pre(lineserializer.get_line()) + serializer.end_entity() + + elif elem.tag == 'ul': + serializer.start_block(stylestack) + bullet = stylestack.get('bullet') + for child in elem: + if child.tag == 'li': + serializer.style(stylestack) + serializer.raw(' ' * indent * stylestack.get('tabwidth') + bullet) + print_elem(stylestack,serializer,child,indent=indent+1) + serializer.end_entity() + + elif elem.tag == 'ol': + cwidth = len(str(len(elem))) + 2 + i = 1 + serializer.start_block(stylestack) + for child in elem: + if child.tag == 'li': + serializer.style(stylestack) + serializer.raw(' ' * indent * stylestack.get('tabwidth') + ' ' + (str(i)+')').ljust(cwidth)) + i = i + 1 + print_elem(stylestack,serializer,child,indent=indent+1) + serializer.end_entity() + + elif elem.tag == 'pre': + serializer.start_block(stylestack) + serializer.pre(elem.text) + serializer.end_entity() + + elif elem.tag == 'hr': + width = stylestack.get('width') + if stylestack.get('size') in ('double', 'double-width'): + width = width / 2 + serializer.start_block(stylestack) + serializer.text('-'*width) + serializer.end_entity() + + elif elem.tag == 'br': + serializer.linebreak() + + elif elem.tag == 'img': + if 'src' in elem.attrib and 'data:' in elem.attrib['src']: + self.print_base64_image(elem.attrib['src']) + + elif elem.tag == 'barcode' and 'encoding' in elem.attrib: + serializer.start_block(stylestack) + self.barcode(strclean(elem.text),elem.attrib['encoding']) + serializer.end_entity() + + elif elem.tag == 'cut': + self.cut() + elif elem.tag == 'partialcut': + self.cut(mode='part') + elif elem.tag == 'cashdraw': + self.cashdraw(2) + self.cashdraw(5) + + stylestack.pop() + + try: + stylestack = StyleStack() + serializer = XmlSerializer(self) + root = ET.fromstring(xml) + + self._raw(stylestack.to_escpos()) + + print_elem(stylestack,serializer,root) + + if 'open-cashdrawer' in root.attrib and root.attrib['open-cashdrawer'] == 'true': + self.cashdraw(2) + self.cashdraw(5) + if not 'cut' in root.attrib or root.attrib['cut'] == 'true' : + self.cut() + + except Exception as e: + errmsg = str(e)+'\n'+'-'*48+'\n'+traceback.format_exc() + '-'*48+'\n' + self.text(errmsg) + self.cut() + + raise e + def text(self,txt): """ Print Utf8 encoded alpha-numeric text """ if not txt: diff --git a/addons/hw_posbox_homepage/__init__.py b/addons/hw_posbox_homepage/__init__.py new file mode 100644 index 00000000000..a208bc1c551 --- /dev/null +++ b/addons/hw_posbox_homepage/__init__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +import controllers + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: + diff --git a/addons/hw_posbox_homepage/__openerp__.py b/addons/hw_posbox_homepage/__openerp__.py new file mode 100644 index 00000000000..7c0cac0f370 --- /dev/null +++ b/addons/hw_posbox_homepage/__openerp__.py @@ -0,0 +1,47 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + + +{ + 'name': 'PosBox Homepage', + 'version': '1.0', + 'category': 'Hardware Drivers', + 'sequence': 6, + 'summary': 'A homepage for the PosBox', + 'description': """ +PosBox Homepage +=============== + +This module overrides openerp web interface to display a simple +Homepage that explains what's the posbox and show the status, +and where to find documentation. + +If you activate this module, you won't be able to access the +regular openerp interface anymore. + +""", + 'author': 'OpenERP SA', + 'depends': ['hw_proxy'], + 'installable': False, + 'auto_install': False, +} + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/hw_posbox_homepage/controllers/__init__.py b/addons/hw_posbox_homepage/controllers/__init__.py new file mode 100644 index 00000000000..b5f0bcc9ec6 --- /dev/null +++ b/addons/hw_posbox_homepage/controllers/__init__.py @@ -0,0 +1,3 @@ +import main +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: + diff --git a/addons/hw_posbox_homepage/controllers/main.py b/addons/hw_posbox_homepage/controllers/main.py new file mode 100644 index 00000000000..bab94be060c --- /dev/null +++ b/addons/hw_posbox_homepage/controllers/main.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +import logging +import os +import time +from os import listdir + +import openerp +from openerp import http +from openerp.http import request +from openerp.tools.translate import _ + +_logger = logging.getLogger(__name__) + +index_template = """ + + + + OpenERP's PosBox + + + +

Your PosBox is up and running

+

+ The PosBox is an hardware adapter that allows you to use + receipt printers and barcode scanners with OpenERP's Point of + Sale, version 8.0 or later. You can start an online free trial, + or download and install it yourself. +

+

+ For more information on how to setup the Point of Sale with + the PosBox, please refer to the manual +

+

+ To see the status of the connected hardware, please refer + to the hardware status page +

+

+ The PosBox software installed on this posbox is version 6, + the posbox version number is independent from OpenERP. You can upgrade + the software on the upgrade page +

+

For any other question, please contact the OpenERP support at support@openerp.com +

+ + + +""" + + +class PosboxHomepage(openerp.addons.web.controllers.main.Home): + @http.route('/', type='http', auth='none', website=True) + def index(self): + #return request.render('hw_posbox_homepage.index',mimetype='text/html') + return index_template + diff --git a/addons/hw_posbox_upgrade/__init__.py b/addons/hw_posbox_upgrade/__init__.py new file mode 100644 index 00000000000..a208bc1c551 --- /dev/null +++ b/addons/hw_posbox_upgrade/__init__.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + +import controllers + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: + diff --git a/addons/hw_posbox_upgrade/__openerp__.py b/addons/hw_posbox_upgrade/__openerp__.py new file mode 100644 index 00000000000..68f81c2079b --- /dev/null +++ b/addons/hw_posbox_upgrade/__openerp__.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SPRL (). +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as +# published by the Free Software Foundation, either version 3 of the +# License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Affero General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see . +# +############################################################################## + + +{ + 'name': 'PosBox Software Upgrader', + 'version': '1.0', + 'category': 'Hardware Drivers', + 'sequence': 6, + 'summary': 'Allows to remotely upgrade the PosBox software', + 'description': """ +PosBox Software Upgrader +======================== + +This module allows to remotely upgrade the PosBox software to a +new version. This module is specific to the PosBox setup and environment +and should not be installed on regular openerp servers. + +""", + 'author': 'OpenERP SA', + 'depends': ['hw_proxy'], + 'test': [ + ], + 'installable': False, + 'auto_install': False, +} + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/hw_posbox_upgrade/controllers/__init__.py b/addons/hw_posbox_upgrade/controllers/__init__.py new file mode 100644 index 00000000000..b5f0bcc9ec6 --- /dev/null +++ b/addons/hw_posbox_upgrade/controllers/__init__.py @@ -0,0 +1,3 @@ +import main +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: + diff --git a/addons/hw_posbox_upgrade/controllers/main.py b/addons/hw_posbox_upgrade/controllers/main.py new file mode 100644 index 00000000000..b741a7a9102 --- /dev/null +++ b/addons/hw_posbox_upgrade/controllers/main.py @@ -0,0 +1,126 @@ +# -*- coding: utf-8 -*- +import logging +import os +import time + +import openerp +import openerp.addons.hw_proxy.controllers.main as hw_proxy +import threading +from openerp import http +from openerp.http import request +from openerp.tools.translate import _ + +_logger = logging.getLogger(__name__) + +upgrade_template = """ + + + + OpenERP's PosBox - Software Upgrade + + + + + +

PosBox Software Upgrade

+

+ This tool will help you perform an upgrade of the PosBox's software. + However the preferred method to upgrade the posbox is to flash the sd-card with + the latest image. The upgrade + procedure is explained into to the PosBox manual +

+

+ To upgrade the posbox, click on the upgrade button. The upgrade will take a few minutes. Do not reboot the PosBox during the upgrade. +

+
+ Upgrade +
+ + + +""" + +class PosboxUpgrader(hw_proxy.Proxy): + def __init__(self): + super(PosboxUpgrader,self).__init__() + self.upgrading = threading.Lock() + self.last_upgrade = 0 + + @http.route('/hw_proxy/upgrade', type='http', auth='none', ) + def upgrade(self): + return upgrade_template + + @http.route('/hw_proxy/perform_upgrade', type='http', auth='none') + def perform_upgrade(self): + self.upgrading.acquire() + if time.time() - self.last_upgrade < 30: + self.upgrading.release() + return 'UPTODATE' + else: + os.system('/bin/bash /home/pi/openerp/update.sh') + self.last_upgrade = time.time() + self.upgrading.release() + return 'SUCCESS' + + @http.route('/hw_proxy/perform_restart', type='http', auth='none') + def perform_restart(self): + self.upgrading.acquire() + if time.time() - self.last_upgrade < 30: + self.upgrading.release() + return 'RESTARTED' + else: + os.system('/bin/bash /home/pi/openerp/restart.sh') + self.last_upgrade = time.time() + self.upgrading.release() + return 'SUCCESS' + + diff --git a/addons/hw_proxy/controllers/main.py b/addons/hw_proxy/controllers/main.py index 5293abd1f25..9e94e4817a4 100644 --- a/addons/hw_proxy/controllers/main.py +++ b/addons/hw_proxy/controllers/main.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import logging +import commands import simplejson import os import os.path @@ -43,7 +44,32 @@ class Proxy(http.Controller): @http.route('/hw_proxy/status', type='http', auth='none', cors='*') def status_http(self): - resp = '\n\n

Hardware Proxy Status

\n' + resp = """ + + + + OpenERP's PosBox + + + +

Hardware Status

+

The list of enabled drivers and their status

+""" statuses = self.get_status() for driver in statuses: @@ -56,12 +82,22 @@ class Proxy(http.Controller): else: color = 'red' - resp += "

"+driver+' : '+status['status']+"

\n" + resp += "

"+driver+' : '+status['status']+"

\n" resp += "
    \n" for msg in status['messages']: resp += '
  • '+msg+'
  • \n' resp += "
\n" - resp += "\n\n\n\n" + resp += """ +

Connected Devices

+

The list of connected USB devices as seen by the posbox

+ """ + devices = commands.getoutput("lsusb").split('\n') + resp += "
\n" + for device in devices: + device_name = device[device.find('ID')+2:] + resp+= "
"+device_name+"
\n" + resp += "
\n" + resp += "\n\n\n" return request.make_response(resp,{ 'Cache-Control': 'no-cache', diff --git a/addons/hw_proxy/static/doc/manual.pdf b/addons/hw_proxy/static/doc/manual.pdf new file mode 100644 index 00000000000..5a55c56654d Binary files /dev/null and b/addons/hw_proxy/static/doc/manual.pdf differ diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js index 5db91924bce..f46b156371c 100644 --- a/addons/point_of_sale/static/src/js/devices.js +++ b/addons/point_of_sale/static/src/js/devices.js @@ -511,43 +511,8 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal return this.message('open_cashbox'); }, - /* ask the printer to print a receipt - * receipt is a JSON object with the following specs: - * receipt{ - * - orderlines : list of orderlines : - * { - * quantity: (number) the number of items, or the weight, - * unit_name: (string) the name of the item's unit (kg, dozen, ...) - * price: (number) the price of one unit of the item before discount - * discount: (number) the discount on the product in % [0,100] - * product_name: (string) the name of the product - * price_with_tax: (number) the price paid for this orderline, tax included - * price_without_tax: (number) the price paid for this orderline, without taxes - * tax: (number) the price paid in taxes on this orderline - * product_description: (string) generic description of the product - * product_description_sale: (string) sales related information of the product - * } - * - paymentlines : list of paymentlines : - * { - * amount: (number) the amount paid - * journal: (string) the name of the journal on wich the payment has been made - * } - * - total_with_tax: (number) the total of the receipt tax included - * - total_without_tax: (number) the total of the receipt without taxes - * - total_tax: (number) the total amount of taxes paid - * - total_paid: (number) the total sum paid by the client - * - change: (number) the amount of change given back to the client - * - name: (string) a unique name for this order - * - client: (string) name of the client. or null if no client is logged - * - cashier: (string) the name of the cashier - * - date: { the date at wich the payment has been done - * year: (number) the year [2012, ...] - * month: (number) the month [0,11] - * date: (number) the day of the month [1,31] - * day: (number) the day of the week [0,6] - * hour: (number) the hour [0,23] - * minute: (number) the minute [0,59] - * } + /* + * ask the printer to print a receipt */ print_receipt: function(receipt){ var self = this; @@ -558,7 +523,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal function send_printing_job(){ if (self.receipt_queue.length > 0){ var r = self.receipt_queue.shift(); - self.message('print_receipt',{ receipt: r },{ timeout: 5000 }) + self.message('print_xml_receipt',{ receipt: r },{ timeout: 5000 }) .then(function(){ send_printing_job(); },function(){ diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index c8c87f1b3af..8de82b4dc0a 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -987,6 +987,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal hour: date.getHours(), minute: date.getMinutes() , isostring: date.toISOString(), + localestring: date.toLocaleString(), }, company:{ email: company.email, diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index 937ee3f88d4..1d6f018ae1b 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -1172,7 +1172,10 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa }else{ this.pos.push_order(currentOrder) if(this.pos.config.iface_print_via_proxy){ - this.pos.proxy.print_receipt(currentOrder.export_for_printing()); + var receipt = currentOrder.export_for_printing(); + this.pos.proxy.print_receipt(QWeb.render('XmlReceipt',{ + receipt: receipt + })); this.pos.get('selectedOrder').destroy(); //finish order and go back to scan screen }else{ this.pos_widget.screen_selector.set_current_screen(this.next_screen); diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index d0cd945d4f4..baea81becb7 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -319,6 +319,155 @@ + + + + +
+
+ +

+
+
+
+ +
+
+ +
+
+ +
Tel:
+
+ +
VAT:
+
+ +
+
+ +
+
+ +
+
+ +
--------------------------------
+
Served by
+
+
+

+ + + +
+ + + + + + + + + + + + Discount: % + + + + + + + + + + x + + + + + + + + + + +
+ + + + + + -------- + Subtotal receipt.subtotal + + + + + + + + + + + ------- + +
        TOTAL
+ +
+

+ + + + + + + + + +
+ + +
        CHANGE
+ +
+
+ + + + + + Discounts + + + + + + + + + + + + + + +
+
+
+
+
+ +
+
+
+
+
+ +
+
+

Welcome