From d0f5463a7ac3f0eef5dc3e6b259029b2b09177a9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Au=C3=A9lien=20DUMAINE?= Date: Wed, 9 Jul 2014 14:20:20 +0200 Subject: [PATCH] Initial check-in of the module pos_customer_display. --- pos_customer_display/__init__.py | 1 + pos_customer_display/__openerp__.py | 46 +++ .../customer_display_view.xml | 16 ++ pos_customer_display/pos_customer_display.py | 46 +++ pos_customer_display/pos_customer_display.xml | 10 + .../static/src/js/customer_display.js | 264 ++++++++++++++++++ 6 files changed, 383 insertions(+) create mode 100755 pos_customer_display/__init__.py create mode 100755 pos_customer_display/__openerp__.py create mode 100644 pos_customer_display/customer_display_view.xml create mode 100644 pos_customer_display/pos_customer_display.py create mode 100755 pos_customer_display/pos_customer_display.xml create mode 100755 pos_customer_display/static/src/js/customer_display.js diff --git a/pos_customer_display/__init__.py b/pos_customer_display/__init__.py new file mode 100755 index 00000000..7dff50a7 --- /dev/null +++ b/pos_customer_display/__init__.py @@ -0,0 +1 @@ +import pos_customer_display diff --git a/pos_customer_display/__openerp__.py b/pos_customer_display/__openerp__.py new file mode 100755 index 00000000..3fb094b6 --- /dev/null +++ b/pos_customer_display/__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': 'POS Customer Display', + 'version': '0.1', + 'category': 'Point Of Sale', + 'summary': 'Manage Customer Display device from POS front end', + 'description': """ +POS Customer Display +==================== + +This module adds support for Customer Display in the Point of Sale. This module is designed to be installed on the *main Odoo server*. On the *POSbox*, you should install the module *hw_customer_display*. + +The number of rows and cols of the Customer Display (usually 2 x 20) should be configured on the main Odoo server, in the menu Point of Sale > Configuration > Point of Sales. + +It has been tested with a Bixolon BCD-1100 (http://www.bixolon.com/html/en/product/product_detail.xhtml?prod_id=61), but should support most serial and USB-serial LCD displays out-of-the-box or with inheritance of a few functions. To setup the BCD-1100 on Linux, you will find some technical instructions on this page : http://techtuxwords.blogspot.fr/2012/12/linux-and-bixolon-bcd-1100.html + +This module has been developped during a POS code sprint at Akretion France from July 7th to July 10th 2014. This module is part of the POS project of the Odoo Community Association http://odoo-community.org/. You are invited to become a member and/or get involved in the Association ! + +Please contact Alexis de Lattre from Akretion for any help or question about this module. + """, + 'author': 'Aurélien DUMAINE', + 'depends': ['point_of_sale'], + 'data' : ['pos_customer_display.xml', + 'customer_display_view.xml'], +} diff --git a/pos_customer_display/customer_display_view.xml b/pos_customer_display/customer_display_view.xml new file mode 100644 index 00000000..80b238a6 --- /dev/null +++ b/pos_customer_display/customer_display_view.xml @@ -0,0 +1,16 @@ + + + + + pos.config.form.view.inherit + pos.config + + + + + + + + + + diff --git a/pos_customer_display/pos_customer_display.py b/pos_customer_display/pos_customer_display.py new file mode 100644 index 00000000..20eda083 --- /dev/null +++ b/pos_customer_display/pos_customer_display.py @@ -0,0 +1,46 @@ +# -*- coding: utf-8 -*- +############################################################################## +# +# OpenERP, Open Source Management Solution +# Copyright (C) 2004-2010 Tiny SP (). +# +# 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 logging +import time + +from openerp import tools +from openerp.osv import fields, osv +from openerp.tools.translate import _ + +import openerp.addons.decimal_precision as dp +import openerp.addons.product.product + +_logger = logging.getLogger(__name__) + +class pos_config(osv.osv): + _name = 'pos.config' + _inherit = 'pos.config' + + _columns = { + 'iface_customer_display' : fields.boolean('Customer display', help="Display data on the customer display"), + 'customer_display_line_length' : fields.integer('Line length', help="Length of the LEDs lines of the customer display"), + } + _defaults = { + 'customer_display_line_length' : 20, + } + +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/pos_customer_display/pos_customer_display.xml b/pos_customer_display/pos_customer_display.xml new file mode 100755 index 00000000..3aaa679e --- /dev/null +++ b/pos_customer_display/pos_customer_display.xml @@ -0,0 +1,10 @@ + + + + + + diff --git a/pos_customer_display/static/src/js/customer_display.js b/pos_customer_display/static/src/js/customer_display.js new file mode 100755 index 00000000..73dc914a --- /dev/null +++ b/pos_customer_display/static/src/js/customer_display.js @@ -0,0 +1,264 @@ +openerp.pos_customer_display = function(instance){ + module = instance.point_of_sale; + + var _t = instance.web._t; + var round_di = instance.web.round_decimals; + var round_pr = instance.web.round_precision + + + module.PosModel = module.PosModel.extend({ + prepare_text_customer_display: function(type, data){ + if (this.config.iface_customer_display != true) + return; + var line_length = this.config.customer_display_line_length || 20; + var currency_rounding = Math.ceil(Math.log(1.0 / this.currency.rounding) / Math.log(10)); + + if (type == 'addProduct'){ + // in order to not recompute qty in options..., we assume that the new ordeLine is the last of the collection + // addOrderline exists but is not called by addProduct, should we handle it ? + var line = this.get('selectedOrder').getLastOrderline(); + var price_unit = line.get_quantity() * line.get_unit_price() * (1.0 - (line.get_discount() / 100.0)); + price_unit = price_unit.toFixed(currency_rounding); + var l21 = line.get_quantity_str_with_unit() + ' x ' + price_unit; + var l22 = ' ' + line.get_display_price().toFixed(currency_rounding); + var lines_to_send = new Array( + this.proxy.complete_string_right(line.get_product().name, line_length), + this.proxy.complete_string_right(l21, line_length - l22.length) + l22 + ); + } else if (type == 'removeOrderline') { + // FIXME : first click on the backspace button set the amount to 0 + var line = data['line']; + var price_unit = line.get_quantity() * line.get_unit_price() * (1.0 - (line.get_discount() / 100.0)); + price_unit = price_unit.toFixed(currency_rounding); + var l21 = '-' + line.get_quantity_str_with_unit() + ' x ' + price_unit; + var l22 = ' ' + -1 * line.get_display_price().toFixed(currency_rounding); + var lines_to_send = new Array( + this.proxy.complete_string_right(line.get_product().name, line_length), + this.proxy.complete_string_right(l21, line_length - l22.length) + l22 + ); + } else if (type == 'addPaymentline') { + var cashregister = data['cashregister']; + var total = this.get('selectedOrder').getTotalTaxIncluded().toFixed(currency_rounding); + var lines_to_send = new Array( + this.proxy.complete_string_right(_t("TOTAL : "), line_length - 1 - total.length) + ' ' + total, + this.proxy.complete_string_right(_t("Paiement :"), line_length - 1 - cashregister.journal_id[1].length) + ' ' + cashregister.journal_id[1] + ); + + } else if (type == 'removePaymentline') { + var line = data['line']; + var amount = line.get_amount().toFixed(currency_rounding); + var lines_to_send = new Array( + this.proxy.complete_string_right(_t("Suppression paiement"), line_length), + this.proxy.complete_string_right(line.cashregister.journal_id[1] , line_length - 1- amount.length) + ' ' + amount + ); + } else if (type == 'pushOrder') { + var currentOrder = data['currentOrder']; + var paidTotal = currentOrder.getPaidTotal(); + var dueTotal = currentOrder.getTotalTaxIncluded(); + var remaining = dueTotal > paidTotal ? dueTotal - paidTotal : 0; + var change = paidTotal > dueTotal ? paidTotal - dueTotal : 0; + + var l1; + if (change == 0){ + l1 = this.proxy.complete_string_center(_t(""), line_length); + } else { + change = change.toFixed(currency_rounding); + l1 = this.proxy.complete_string_right(_t("YOUR CHANGE :"), line_length - 1 - change.length) + ' ' + change; + } + + var lines_to_send = new Array( + l1, + this.proxy.complete_string_center(_t("Next customer..."), line_length) + ); + } else if (type = 'closePOS') { + var lines_to_send = new Array( + this.proxy.complete_string_center(_t("Point of sale closed"), line_length), + this.proxy.complete_string_center(_t("***"), line_length) + ); + } else { + console.warn('Unknown message type'); + return; + } + + this.proxy.send_text_customer_display(lines_to_send, line_length); + }, + + }); + + + module.ProxyDevice = module.ProxyDevice.extend({ + send_text_customer_display: function(data, line_length){ + if (data[0].length != line_length || data[1].length != line_length){ + console.warn("Data components have to have " + line_length + " chars."); + console.log(data[0].length + " -> "+ data[0] + "\n" + data[1].length + " -> " + data[1]); + } else { + //alert(JSON.stringify(data)); + return this.message('send_text_customer_display', {'text_to_display' : JSON.stringify(data)}); + } + return; + }, + complete_string_right: function(string, length){ + if (string.length > length) + { + return string.substring(0,length); + } + else if (string.length < length) + { + while(string.length < length) + string = string+' '; + return string; + } + return string; + }, + complete_string_left: function(string, length){ + if (string.length > length) + { + return string.substring(0,length); + } + else if (string.length < length) + { + while(string.length < length) + string = ' '+string; + return string; + } + return string; + }, + complete_string_center: function(string, length){ + var self = this; + if (string.length > length) + { + return string.substring(0,length); + } + else if (string.length < length) + { + ini = (length - string.length)/2; + while(string.length < length - ini) + string = ' '+string; + while(string.length < length) + string = string + ' '; + return string; + } + return string; + }, + }); + + + var _super_setSmartStatus_ = module.ProxyStatusWidget.prototype.set_smart_status; + module.ProxyStatusWidget.prototype.set_smart_status = function(status){ + _super_setSmartStatus_.call(this, status); + if(status.status === 'connected'){ + var warning = false; + var msg = '' + if( this.pos.config.iface_customer_display){ + var customer_display = status.drivers.customer_display ? status.drivers.customer_display.status : false; + if( customer_display != 'connected' && customer_display != 'connecting'){ + warning = true; + msg = msg ? msg + ' & ' : msg; + msg += _t('Customer display'); + } + } + msg = msg ? msg + ' ' + _t('Offline') : msg; + this.set_status(warning ? 'warning' : 'connected', msg); + }else{ + this.set_status(status.status,''); + } + }; + + + var _super_addProduct_ = module.Order.prototype.addProduct; + module.Order.prototype.addProduct = function(product, options){ + _super_addProduct_.call(this, product, options); + this.pos.prepare_text_customer_display('addProduct', {'product' : product, 'options' : options}); + }; + + + var _super_removeOrderline_ = module.Order.prototype.removeOrderline; + module.Order.prototype.removeOrderline = function(line){ + this.pos.prepare_text_customer_display('removeOrderline', {'line' : line}); + _super_removeOrderline_.call(this, line); + }; + + var _super_removePaymentline_ = module.Order.prototype.removePaymentline; + module.Order.prototype.removePaymentline = function(line){ + this.pos.prepare_text_customer_display('removePaymentline', {'line' : line}); + _super_removePaymentline_.call(this, line); + }; + + var _super_addPaymentline_ = module.Order.prototype.addPaymentline; + module.Order.prototype.addPaymentline = function(cashregister){ + _super_addPaymentline_.call(this, cashregister); + this.pos.prepare_text_customer_display('addPaymentline', {'cashregister' : cashregister}); + }; + + + var _super_pushOrder_ = module.PosModel.prototype.push_order; + module.PosModel.prototype.push_order = function(currentOrder){ + _super_pushOrder_.call(this, currentOrder); + this.prepare_text_customer_display('pushOrder', {'currentOrder' : currentOrder}); + + }; + + + var _super_closePOS_ = module.PosWidget.prototype.close; + module.PosWidget.prototype.close = function(){ + this.pos.prepare_text_customer_display('closePOS', {}); + _super_closePOS_.call(this); + }; + +/* + var _super_updatePayment_ = module.PaymentScreenWidget.prototype.update_payment_summary; + module.PaymentScreenWidget.prototype.update_payment_summary = function(){ + _super_updatePayement_.call(this); + if( this.pos.config.iface_customer_display != true ) + return; + var currentOrder = this.pos.get('selectedOrder'); + var paidTotal = currentOrder.getPaidTotal(); + var dueTotal = currentOrder.getTotalTaxIncluded(); + var remaining = dueTotal > paidTotal ? dueTotal - paidTotal : 0; + var change = paidTotal > dueTotal ? paidTotal - dueTotal : 0; + + var currency_rounding = Math.ceil(Math.log(1.0 / this.pos.currency.rounding) / Math.log(10)); + var amount = line.get_amount().toFixed(currency_rounding); + var line_length = this.pos.config.customer_display_line_length || 20; + var data = new Array( + this.pos.proxy.complete_string_right(_t("Suppression paiement"), line_length), + this.pos.proxy.complete_string_right(line.cashregister.journal_id[1] , line_length - amount.length) + ' ' + amount + ); + this.pos.proxy.send_text_customer_display(data); + }; +*/ +/* + module.Paymentline = module.Paymentline.extend({ + set_pos: function(pos){ + this.pos = pos; + }, + }); + + module.PaypadButtonWidget = module.PaypadButtonWidget.extend({ + renderElement: function() { + var self = this; + this._super(); + + this.$el.click(function(){ + if (self.pos.get('selectedOrder').get('screen') === 'receipt'){ //TODO Why ? + console.warn('TODO should not get there...?'); + return; + } + self.pos.get('selectedOrder').addPaymentline(self.cashregister); + console.log('aurel'); + self.pos.get('selectedOrder').get('paymentLines').at(self.pos.get('selectedOrder').get('paymentLines').length -1).set_pos(self.pos); + console.log('aurel'); + self.pos_widget.screen_selector.set_current_screen('payment'); + }); + }, + }); + + var _super_setamountPaymentline_ = module.Paymentline.prototype.set_amount; + module.Paymentline.prototype.set_amount = function(amount){ + _super_setamountPaymentline_.call(this, amount); + var data = _t("Add paymentline : ") + this.cashregister.journal_id[1] + " " + this.get_amount() + " " + this.pos.get('selectedOrder').getDueLeft(); + alert(data); + this.pos.proxy.send_text_customer_display(data); + }; +*/ +};