diff --git a/addons/hw_escpos/escpos/escpos.py b/addons/hw_escpos/escpos/escpos.py index 8b6a94c04ba..13ea11af7a6 100644 --- a/addons/hw_escpos/escpos/escpos.py +++ b/addons/hw_escpos/escpos/escpos.py @@ -682,7 +682,7 @@ class Escpos: try: stylestack = StyleStack() serializer = XmlSerializer(self) - root = ET.fromstring(xml) + root = ET.fromstring(xml.encode('utf-8')) self._raw(stylestack.to_escpos()) diff --git a/addons/point_of_sale/__openerp__.py b/addons/point_of_sale/__openerp__.py index 79b4cbeebbf..4e378d0a494 100644 --- a/addons/point_of_sale/__openerp__.py +++ b/addons/point_of_sale/__openerp__.py @@ -60,6 +60,7 @@ Main Features 'wizard/pos_open_statement.xml', 'wizard/pos_payment.xml', 'wizard/pos_session_opening.xml', + 'views/templates.xml', 'point_of_sale_report.xml', 'point_of_sale_view.xml', 'point_of_sale_data.xml', diff --git a/addons/point_of_sale/controllers/main.py b/addons/point_of_sale/controllers/main.py index 9f1887eb361..fb2a971d041 100644 --- a/addons/point_of_sale/controllers/main.py +++ b/addons/point_of_sale/controllers/main.py @@ -12,55 +12,6 @@ from openerp.addons.web.controllers.main import module_boot, login_redirect _logger = logging.getLogger(__name__) -html_template = """ - - - OpenERP POS - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - -
-
-
- - - - -""" class PosController(http.Controller): @@ -70,15 +21,19 @@ class PosController(http.Controller): if not request.session.uid: return login_redirect() - r = html_template % { - 'modules': simplejson.dumps(module_boot(request.db)), - 'init': """ - var wc = new s.web.WebClient(); - wc.show_application = function(){ - wc.action_manager.do_action("pos.ui"); - }; - wc.setElement($(document.body)); - wc.start(); - """ - } - return r + modules = simplejson.dumps(module_boot(request.db)) + init = """ + var wc = new s.web.WebClient(); + wc.show_application = function(){ + wc.action_manager.do_action("pos.ui"); + }; + wc.setElement($(document.body)); + wc.start(); + """ + + html = request.registry.get('ir.ui.view').render(request.cr, request.session.uid,'point_of_sale.index',{ + 'modules': modules, + 'init': init, + }) + + return html diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index 7430dcfa13f..6bef9f77248 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -88,7 +88,13 @@ class pos_config(osv.osv): 'session_ids': fields.one2many('pos.session', 'config_id', 'Sessions'), 'group_by' : fields.boolean('Group Journal Items', help="Check this if you want to group the Journal Items by Product while closing a Session"), 'pricelist_id': fields.many2one('product.pricelist','Pricelist', required=True), - 'company_id': fields.many2one('res.company', 'Company', required=True), + 'company_id': fields.many2one('res.company', 'Company', required=True), + 'barcode_product': fields.char('Product Barcodes', size=64, help='The pattern that identifies product barcodes'), + 'barcode_cashier': fields.char('Cashier Barcodes', size=64, help='The pattern that identifies cashier login barcodes'), + 'barcode_customer': fields.char('Customer Barcodes',size=64, help='The pattern that identifies customer\'s client card barcodes'), + 'barcode_price': fields.char('Price Barcodes', size=64, help='The pattern that identifies a product with a barcode encoded price'), + 'barcode_weight': fields.char('Weight Barcodes', size=64, help='The pattern that identifies a product with a barcode encoded weight'), + 'barcode_discount': fields.char('Discount Barcodes', size=64, help='The pattern that identifies a product with a barcode encoded discount'), } def _check_cash_control(self, cr, uid, ids, context=None): @@ -156,6 +162,12 @@ class pos_config(osv.osv): 'iface_invoicing': True, 'stock_location_id': _get_default_location, 'company_id': _get_default_company, + 'barcode_product': '*', + 'barcode_cashier': '041*', + 'barcode_customer':'042*', + 'barcode_weight': '21xxxxxNNDDD', + 'barcode_discount':'22xxxxxxxxNN', + 'barcode_price': '23xxxxxNNNDD', } def onchange_picking_type_id(self, cr, uid, ids, picking_type_id, context=None): @@ -241,6 +253,8 @@ class pos_session(osv.osv): 'state' : fields.selection(POS_SESSION_STATE, 'Status', required=True, readonly=True, select=1), + + 'sequence_number': fields.integer('Order Sequence Number'), 'cash_control' : fields.function(_compute_cash_all, multi='cash', @@ -303,6 +317,7 @@ class pos_session(osv.osv): 'name' : '/', 'user_id' : lambda obj, cr, uid, context: uid, 'state' : 'opening_control', + 'sequence_number': 1, } _sql_constraints = [ @@ -554,13 +569,17 @@ class pos_order(osv.osv): for tmp_order in orders_to_save: to_invoice = tmp_order['to_invoice'] order = tmp_order['data'] - order_id = self.create(cr, uid, self._order_fields(cr, uid, order, context=context),context) + for payments in order['statement_ids']: self.add_payment(cr, uid, order_id, self._payment_fields(cr, uid, payments[2], context=context), context=context) + session = self.pool.get('pos.session').browse(cr, uid, order['pos_session_id'], context=context) + if session.sequence_number <= order['sequence_number']: + session.write({'sequence_number': order['sequence_number'] + 1}) + session.refresh() + if order['amount_return']: - session = self.pool.get('pos.session').browse(cr, uid, order['pos_session_id'], context=context) cash_journal = session.cash_journal_id if not cash_journal: cash_journal_ids = filter(lambda st: st.journal_id.type=='cash', session.statement_ids) @@ -667,6 +686,7 @@ class pos_order(osv.osv): '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), 'partner_id': fields.many2one('res.partner', 'Customer', change_default=True, select=1, states={'draft': [('readonly', False)], 'paid': [('readonly', False)]}), + 'sequence_number': fields.integer('Sequence Number', help='A session-unique sequence number for the order'), 'session_id' : fields.many2one('pos.session', 'Session', #required=True, @@ -715,6 +735,7 @@ class pos_order(osv.osv): 'name': '/', 'date_order': lambda *a: time.strftime('%Y-%m-%d %H:%M:%S'), 'nb_print': 0, + 'sequence_number': 1, 'session_id': _default_session, 'company_id': lambda self,cr,uid,c: self.pool.get('res.users').browse(cr, uid, uid, c).company_id.id, 'pricelist_id': _default_pricelist, diff --git a/addons/point_of_sale/point_of_sale_view.xml b/addons/point_of_sale/point_of_sale_view.xml index af0a815ad8d..07ec934318e 100644 --- a/addons/point_of_sale/point_of_sale_view.xml +++ b/addons/point_of_sale/point_of_sale_view.xml @@ -672,6 +672,14 @@ + + + + + + + + @@ -703,6 +711,7 @@ + .fa { + font-size: 16px; + vertical-align: middle; + margin-right: 4px; +} +.pos .order-button .order-sequence{ + font-size: 16px; + font-weight: 800; } .pos .order-selector { @@ -420,6 +461,46 @@ td { background: #F0EEEE; white-space: nowrap; } +/* ********* The control buttons ********* */ + +.pos .control-buttons { + padding-top: 8px; +} +.pos .control-button { + background: #e2e2e2; + border: solid 1px #BEBEBE; + display: inline-block; + line-height: 38px; + min-width: 38px; + text-align: center; + border-radius: 3px; + padding: 0px 10px; + font-size: 18px; + margin-left: 6px; + cursor: pointer; +} +.pos .control-button .fa{ + margin-right: 4px; +} + +.pos .control-button.highlight{ + background: #6EC89B; + border: solid 1px #6EC89B; + color: white; +} +.pos .control-button:active { + background: #7F82AC; + border: solid 1px #7F82AC; + color: white; +} +.pos .control-button.disabled, +.pos .control-button.disabled:active{ + background: #e2e2e2; + border: solid 1px #BEBEBE; + opacity: 0.5; + cursor: default; + color: inherit; +} /* ********* The paypad contains the payment buttons ********* */ @@ -782,6 +863,78 @@ td { color: #5a5a5a; } +/* a) Generic Screen Layout Constructs */ + +.screen .screen-content{ + margin: 0px auto; + max-width: 1024px; + text-align: left; + height: 100%; + overflow: hidden; + position: relative; +} +.screen .top-content{ + position: absolute; + left: 0px; top: 0px; right: 0px; + height: 64px; + border-bottom: dashed 1px rgb(215,215,215); + text-align: center; +} +.screen .top-content .button { + position: absolute; + top: 0px; + line-height: 32px; + padding: 3px 13px; + font-size: 20px; + background: rgb(230, 230, 230); + margin: 12px; + border-radius: 3px; + border: solid 1px rgb(209, 209, 209); + cursor: pointer; +} +.screen .top-content .button.highlight{ + background: rgb(110,200,155); + color: white; + border: solid 1px rgb(110,200,155); +} +.screen .top-content .button.back { + left: 0px; + margin-left: 16px; +} +.screen .top-content .button.next{ + right: 0px; + margin-right: 16px; +} +.screen .left-content{ + position: absolute; + left:0px; top: 64px; bottom: 0px; + right:50%; + overflow-x: hidden; + overflow-y: auto; + border-right: dashed 1px rgb(215,215,215); +} +.screen .right-content{ + position: absolute; + right:0px; top: 64px; bottom: 0px; + left:50%; + overflow-x: hidden; + overflow-y: auto; +} +.screen .centered-content{ + position: absolute; + right:25%; top: 64px; bottom: 0px; + left:25%; + border-right: dashed 1px rgb(215,215,215); + border-left: dashed 1px rgb(215,215,215); + overflow-x: hidden; + overflow-y: auto; +} +.screen .full-content{ + position: absolute; + right: 0%; top: 65px; bottom: 0px; + left: 0%; +} + /* a) Layout for the Product Screen */ .pos .screen .layout-table { @@ -995,117 +1148,145 @@ td { /* d) The Scale screen */ -.pos .scale-screen .display{ - position:relative; - width: 550px; - height:190px; - margin-top: 100px; - margin-left: auto; - margin-right: auto; -} - -.pos .scale-screen .product-picture { - position: relative; - -webkit-box-sizing: border-box; - -moz-box-sizing: border-box; - -ms-box-sizing: border-box; - box-sizing: border-box; - display: float; - float: right; - margin: 5px; - width: 180px; - height:180px; - line-height:180px; - cursor:pointer; - - background:#fff; - border: 1px solid #fff; - border-radius: 3px; - box-shadow: 0px 2px rgb(228, 228, 228); -} - -.pos .scale-screen .product-picture img{ - max-width: 178px; - max-height: 178px; - vertical-align: middle; - cursor:pointer; -} - -.pos .scale-screen .product-picture .product-price{ - position: absolute; - top:8px; - right:8px; - width:auto; - height:auto; - line-height:1; - color:white; - background: #7f82ac; - padding: 4px 5px; - border-radius: 3px; - cursor:pointer; -} - -.pos .scale-screen .product-name { - position: absolute; - left: 0px; - right: 210px; - top: 10px; - height: 50px; - font-size: 36px; - line-height: 50px; - text-align: right; - color: #8d8d8d; - overflow: hidden; - text-overflow: ellipsis; -} -.pos .scale-screen .weight{ - position: absolute; - left: 0; - top: 80px; - height: 90px; - bottom: 55px; - right: 210px; - padding: 6px; - padding-right: 33px; - padding-top: 11px; - background: white; - border-radius: 3px; - box-shadow: 0px 2px rgb(228, 228, 228) inset; -} -.pos .scale-screen .weight p{ +.pos .scale-screen .product-price{ + font-size: 25px; + margin: 16px; + text-align: center; display: inline-block; - text-align:right; - line-height: 90px; - font-size: 56px; - width: 100%; - height: 100%; - margin: 0; - margin-right: 18px; - font-family: "Inconsolata"; - color: #6c6c6c; + width: 40%; +} +.pos .scale-screen .computed-price{ + font-size: 25px; + display: inline-block; + text-align: right; + margin: 16px; + margin-top: 0px; + padding: 16px; + background: white; + width: 40%; + border-radius: 3px; + font-family: Inconsolata; + font-weight: bold; + text-shadow: 0px 2px 0px rgb(210,210,210); + box-shadow: 0px 2px 0px rgb(225,225,225) inset; + float: right; +} +.pos .scale-screen .buy-product{ + text-align: center; + font-size: 32px; + background: rgb(110,200,155); + color: white; + border-radius: 3px; + padding: 16px; + margin: 16px; + cursor: pointer; } -/* e) The Welcome Screen */ -.pos .goodbye-message{ - position: absolute; - left:50%; - top:40%; - width:500px; - height:400px; - margin-left: -250px; - margin-top: -200px; - padding:10px; - padding-top:20px; - text-align:center; - font-size:20px; - font-weight:bold; - background-color: #F0EEEE; - border: 1px solid #E0DDDD; +.pos .scale-screen .weight{ + text-align: right; + margin: 16px; + background: white; + padding: 20px; + padding-right: 30px; + font-size: 56px; border-radius: 3px; - box-shadow: 0px 10px 20px rgba(0,0,0,0.4); - z-index:1150; + font-family: Inconsolata; + text-shadow: 0px 2px 0px rgb(210, 210, 210); + box-shadow: 0px 2px 0px rgb(225,225,225) inset; } + +/* e) The Client List Screen */ + +.pos .clientlist-screen .client-list{ + font-size: 16px; + width: 100%; + line-height: 40px; +} +.pos .clientlist-screen .client-list th, +.pos .clientlist-screen .client-list td { + padding: 0px 8px; +} +.pos .clientlist-screen .client-list tr{ + transition: all 150ms linear; + background: rgb(230,230,230); +} +.pos .clientlist-screen .client-list thead > tr, +.pos .clientlist-screen .client-list tr:nth-child(even) { + background: rgb(247,247,247); +} +.pos .clientlist-screen .client-list tr.highlight{ + transition: all 150ms linear; + background: rgb(110,200,155) !important; + color: white; +} +.pos .clientlist-screen .client-list tr.lowlight{ + transition: all 150ms linear; + background: rgb(216, 238, 227); +} +.pos .clientlist-screen .client-list tr.lowlight:nth-child(even){ + transition: all 150ms linear; + background: rgb(227, 246, 237); +} +.pos .clientlist-screen .client-details{ + padding: 16px; + border-bottom: solid 5px rgb(110,200,155); +} +.pos .clientlist-screen .client-picture{ + height: 64px; + width: 64px; + border-radius: 32px; + overflow: hidden; + text-align: center; + float: left; + margin-right: 16px; + background: white; +} +.pos .clientlist-screen .client-picture > img{ + vertical-align: middle; + max-height: 64px; +} +.pos .clientlist-screen .client-name{ + font-size: 32px; + line-height: 64px; + margin-bottom:16px; +} +.pos .clientlist-screen .client-details-box{ + position: relative; + font-size: 16px; +} +.pos .clientlist-screen .client-details-left{ + width: 50%; + float: left; +} +.pos .clientlist-screen .client-details-right{ + width: 50%; + float: right; +} +.pos .clientlist-screen .client-detail{ + line-height: 24px; +} +.pos .clientlist-screen .client-detail > .label{ + font-weight: bold; + display: inline-block; + width: 64px; + text-align: right; + margin-right: 8px; +} +.pos .clientlist-screen .client-detail > .empty{ + opacity: 0.3; +} +.pos .clientlist-screen .searchbox{ + right: auto; + margin-left: -90px; + margin-top:8px; + left: 50%; +} +.pos .clientlist-screen .searchbox input{ + width: 120px; +} + + /* ********* The OrderWidget ********* */ .pos .order-container{ @@ -1119,11 +1300,17 @@ td { .pos .order-scroller{ width:100%; height:100%; - overflow:hidden; + overflow: hidden; overflow-y: auto; -webkit-overflow-scrolling: touch; } +.pos .scrollable-y{ + overflow: hidden !important; + overflow-y: auto !important; + -webkit-overflow-scrolling: touch !important; +} + .pos .order{ background: white; padding-bottom:15px; @@ -1135,6 +1322,8 @@ td { font-size:16px; border-radius: 3px; border: solid 1px rgb(220,220,220); + text-align: left; + max-width: 500px; -webkit-transform: translate3d(0,0,0); } @@ -1172,6 +1361,25 @@ td { color:#999; } +.pos .order .summary .fidpoints{ + position: absolute; + left: 20px; + padding: 10px; + color: #6EC89B; + background: rgba(110, 200, 155, 0.17); + border-radius: 3px; +} + +.submit-kitchen-button { + float: left; + background: rgb(61, 235, 82); + color: white; + padding: 12px 20px; + margin: 0px 15px; + border-radius: 3px; + cursor: pointer; +} + /* ********* The OrderLineWidget ********* */ .pos .order .orderline{ @@ -1231,6 +1439,50 @@ td { font-style:normal; } +/* ********* SplitBill ********* */ + +.splitbill-screen .order-info { + text-align: center; + margin-bottom:20px; + padding: 20px 0px; + font-size: 64px; + color: #43996E; + text-shadow: 0px 2px white, 0px 2px 2px rgba(0, 0, 0, 0.27); + border-bottom: dashed 1px rgb(215,215,215); +} +.splitbill-screen .order .orderline.selected{ + background: rgb(110,200,155); + color: white; + -webkit-transition: background 250ms ease-in-out; + -moz-transition: background 250ms ease-in-out; + transition: background 250ms ease-in-out; + cursor: default; +} +.splitbill-screen .order .orderline.partially.selected{ + background: rgb(136, 214, 176); +} +.splitbill-screen .order .orderline.selected .info-list { + color: white; +} +.splitbill-screen .order .orderline.selected .info-list em{ + color: white; + font-size: 24px; + vertical-align: top; +} +.splitbill-screen .paymentmethods { + margin: 16px; +} +.splitbill-screen .paymentmethod { + background: rgb(221, 221, 221); + line-height: 40px; + margin-bottom: 4px; + border-radius: 3px; + font-size: 16px; + border: solid 1px rgb(202, 202, 202); + cursor: pointer; + text-align: center; +} + /* ********* The ActionBarWidget ********* */ .pos .pos-actionbar{ @@ -1408,6 +1660,11 @@ td { box-shadow: 0px 10px 20px rgba(0,0,0,0.4); z-index:1200; } +.pos .popup .comment{ + font-weight: normal; + font-size: 18px; + margin: 0px 16px; +} .pos .popup .footer{ position:absolute; bottom:0; diff --git a/addons/point_of_sale/static/src/img/logo.png b/addons/point_of_sale/static/src/img/logo.png index 9cf5eb05312..cf6daa51318 100644 Binary files a/addons/point_of_sale/static/src/img/logo.png and b/addons/point_of_sale/static/src/img/logo.png differ diff --git a/addons/point_of_sale/static/src/js/db.js b/addons/point_of_sale/static/src/js/db.js index 1057d774806..38230fb0ca2 100644 --- a/addons/point_of_sale/static/src/js/db.js +++ b/addons/point_of_sale/static/src/js/db.js @@ -21,6 +21,11 @@ function openerp_pos_db(instance, module){ this.product_by_category_id = {}; this.product_by_reference = {}; + this.partners_sorted = []; + this.partner_by_id = {}; + this.partner_by_ean13 = {}; + this.partner_search_string = ""; + this.category_by_id = {}; this.root_category_id = 0; this.category_products = {}; @@ -196,6 +201,60 @@ function openerp_pos_db(instance, module){ } } }, + _partner_search_string: function(partner){ + var str = '' + partner.id + ':' + partner.name; + if(partner.ean13){ + str += '|' + partner.ean13; + } + if(partner.address){ + str += '|' + partner.address; + } + return str + '\n'; + }, + add_partners: function(partners){ + for(var i = 0, len = partners.length; i < len; i++){ + var partner = partners[i]; + this.partner_by_id[partner.id] = partner; + if(partner.ean13){ + this.partner_by_ean13[partner.ean13] = partner; + } + partner.address = (partner.street || '') +', '+ + (partner.zip || '') +' '+ + (partner.city || '') +', '+ + (partner.country_id[1] || ''); + this.partner_search_string += this._partner_search_string(partner); + this.partners_sorted.push(partner); + } + }, + get_partner_by_id: function(id){ + return this.partner_by_id[id]; + }, + get_partner_by_ean13: function(ean13){ + return this.partner_by_ean13[ean13]; + }, + get_partners_sorted: function(){ + return this.partners_sorted; + }, + search_partner: function(query){ + try { + query = query.replace(/[\[\]\(\)\+\*\?\.\-\!\&\^\$\|\~\_\{\}\:\,\\\/]/g,'.'); + query = query.replace(' ','.+'); + var re = RegExp("([0-9]+):.*?"+query,"gi"); + }catch(e){ + return []; + } + var results = []; + for(var i = 0; i < this.limit; i++){ + r = re.exec(this.partner_search_string); + if(r){ + var id = Number(r[1]); + results.push(this.get_partner_by_id(id)); + }else{ + break; + } + } + return results; + }, /* removes all the data from the database. TODO : being able to selectively remove data */ clear: function(stores){ for(var i = 0, len = arguments.length; i < len; i++){ @@ -243,7 +302,13 @@ function openerp_pos_db(instance, module){ * - a name, package or ean13 containing the query (case insensitive) */ search_product_in_category: function(category_id, query){ - var re = RegExp("([0-9]+):.*?"+query,"gi"); + try { + query = query.replace(/[\[\]\(\)\+\*\?\.\-\!\&\^\$\|\~\_\{\}\:\,\\\/]/g,'.'); + query = query.replace(' ','.+'); + var re = RegExp("([0-9]+):.*?"+query,"gi"); + }catch(e){ + return []; + } var results = []; for(var i = 0; i < this.limit; i++){ r = re.exec(this.category_search_string[category_id]); diff --git a/addons/point_of_sale/static/src/js/devices.js b/addons/point_of_sale/static/src/js/devices.js index 2b016445638..23dc8981866 100644 --- a/addons/point_of_sale/static/src/js/devices.js +++ b/addons/point_of_sale/static/src/js/devices.js @@ -77,6 +77,7 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal }; + // this object interfaces with the local proxy to communicate to the various hardware devices // connected to the Point of Sale. As the communication only goes from the POS to the proxy, // methods are used both to signal an event, and to fetch information. @@ -362,28 +363,6 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal this.notifications[name].push(callback); }, - //a product has been scanned and recognized with success - // ean is a parsed ean object - scan_item_success: function(ean){ - return this.message('scan_item_success',{ean: ean}); - }, - - // a product has been scanned but not recognized - // ean is a parsed ean object - scan_item_error_unrecognized: function(ean){ - return this.message('scan_item_error_unrecognized',{ean: ean}); - }, - - //the client is asking for help - help_needed: function(){ - return this.message('help_needed'); - }, - - //the client does not need help anymore - help_canceled: function(){ - return this.message('help_canceled'); - }, - // returns the weight on the scale. scale_read: function(){ var self = this; @@ -411,71 +390,6 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal this.debug_weight = 0; }, - - // the pos asks the client to pay 'price' units - payment_request: function(price){ - var ret = new $.Deferred(); - this.paying = true; - this.custom_payment_status = this.default_payment_status; - return this.message('payment_request',{'price':price}); - }, - - payment_status: function(){ - if(this.bypass_proxy){ - this.bypass_proxy = false; - return (new $.Deferred()).resolve(this.custom_payment_status); - }else{ - return this.message('payment_status'); - } - }, - - // override what the proxy says and accept the payment - debug_accept_payment: function(){ - this.bypass_proxy = true; - this.custom_payment_status = { - status: 'paid', - message: 'Successfull Payment, have a nice day', - payment_method: 'AMEX', - receipt_client: 'bla', - receipt_shop: 'bla', - }; - }, - - // override what the proxy says and reject the payment - debug_reject_payment: function(){ - this.bypass_proxy = true; - this.custom_payment_status = { - status: 'error-rejected', - message: 'Sorry you don\'t have enough money :(', - }; - }, - // the client cancels his payment - payment_cancel: function(){ - this.paying = false; - this.custom_payment_status = 'waiting_for_payment'; - return this.message('payment_cancel'); - }, - - // called when the client logs in or starts to scan product - transaction_start: function(){ - return this.message('transaction_start'); - }, - - // called when the clients has finished his interaction with the machine - transaction_end: function(){ - return this.message('transaction_end'); - }, - - // called when the POS turns to cashier mode - cashier_mode_activated: function(){ - return this.message('cashier_mode_activated'); - }, - - // called when the POS turns to client mode - cashier_mode_deactivated: function(){ - return this.message('cashier_mode_deactivated'); - }, - // ask for the cashbox (the physical box where you store the cash) to be opened open_cashbox: function(){ return this.message('open_cashbox'); @@ -509,10 +423,6 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal return this.message('log',{'arguments': _.toArray(arguments)}); }, - // asks the proxy to print an invoice in pdf form ( used to print invoices generated by the server ) - print_pdf_invoice: function(pdfinvoice){ - return this.message('print_pdf_invoice',{pdfinvoice: pdfinvoice}); - }, }); // this module interfaces with the barcode reader. It assumes the barcode reader @@ -524,8 +434,8 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal 'product', 'cashier', 'client', - 'discount', ], + init: function(attributes){ this.pos = attributes.pos; this.action_callback = {}; @@ -535,11 +445,56 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal this.action_callback_stack = []; - this.weight_prefix_set = attributes.weight_prefix_set || {'21':''}; - this.discount_prefix_set = attributes.discount_prefix_set || {'22':''}; - this.price_prefix_set = attributes.price_prefix_set || {'23':''}; - this.cashier_prefix_set = attributes.cashier_prefix_set || {'041':''}; - this.client_prefix_set = attributes.client_prefix_set || {'042':''}; + this.add_barcode_patterns(attributes.patterns || { + 'product': ['xxxxxxxxxxxxx'], + 'cashier': ['041xxxxxxxxxx'], + 'client': ['042xxxxxxxxxx'], + 'weight': ['21xxxxxNNDDDx'], + 'discount': ['22xxxxxxxxNNx'], + 'price': ['23xxxxxNNNDDx'], + }); + + }, + + add_barcode_patterns: function(patterns){ + this.patterns = this.patterns || {}; + for(type in patterns){ + this.patterns[type] = this.patterns[type] || []; + + var patternlist = patterns[type]; + if( typeof patternlist === 'string'){ + patternlist = patternlist.split(','); + } + for(var i = 0; i < patternlist.length; i++){ + var pattern = patternlist[i].trim().substring(0,12); + if(!pattern.length){ + continue; + } + pattern = pattern.replace(/[x\*]/gi,'x'); + while(pattern.length < 12){ + pattern += 'x'; + } + this.patterns[type].push(pattern); + } + } + + this.sorted_patterns = []; + for (var type in this.patterns){ + var patterns = this.patterns[type]; + for(var i = 0; i < patterns.length; i++){ + var pattern = patterns[i]; + var score = 0; + for(var j = 0; j < pattern.length; j++){ + if(pattern[j] != 'x'){ + score++; + } + } + this.sorted_patterns.push({type:type, pattern:pattern,score:score}); + } + } + this.sorted_patterns.sort(function(a,b){ + return b.score - a.score; + }); }, @@ -625,67 +580,84 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal // most importantly : // - code : the ean // - type : the type of the ean: - // 'price' | 'weight' | 'unit' | 'cashier' | 'client' | 'discount' | 'error' + // 'price' | 'weight' | 'product' | 'cashier' | 'client' | 'discount' | 'error' // - // - prefix : the prefix that has ben used to determine the type - // - id : the part of the ean that identifies something // - value : if the id encodes a numerical value, it will be put there - // - unit : if the encoded value has a unit, it will be put there. - // not to be confused with the 'unit' type, which represent an unit of a - // unique product // - base_code : the ean code with all the encoding parts set to zero; the one put on // the product in the backend parse_ean: function(ean){ + var self = this; var parse_result = { encoding: 'ean13', - type:'unknown', - prefix:'', + type:'error', code:ean, base_code: ean, - id:'', value: 0, - unit: 'none', }; - function match_prefix(prefix_set, type){ - for(prefix in prefix_set){ - if(ean.substring(0,prefix.length) === prefix){ - parse_result.prefix = prefix; - parse_result.type = type; - return true; - } - } - return false; + if (!this.check_ean(ean)){ + return parse_result; } - if (!this.check_ean(ean)){ - parse_result.type = 'error'; - } else if( match_prefix(this.price_prefix_set,'price')){ - parse_result.id = ean.substring(0,7); - parse_result.base_code = this.sanitize_ean(ean.substring(0,7)); - parse_result.value = Number(ean.substring(7,12))/100.0; - parse_result.unit = 'euro'; - } else if( match_prefix(this.weight_prefix_set,'weight')){ - parse_result.id = ean.substring(0,7); - parse_result.value = Number(ean.substring(7,12))/1000.0; - parse_result.base_code = this.sanitize_ean(ean.substring(0,7)); - parse_result.unit = 'Kg'; - } else if( match_prefix(this.client_prefix_set,'client')){ - parse_result.id = ean.substring(0,7); - parse_result.unit = 'Kg'; - } else if( match_prefix(this.cashier_prefix_set,'cashier')){ - parse_result.id = ean.substring(0,7); - } else if( match_prefix(this.discount_prefix_set,'discount')){ - parse_result.id = ean.substring(0,7); - parse_result.base_code = this.sanitize_ean(ean.substring(0,7)); - parse_result.value = Number(ean.substring(7,12))/100.0; - parse_result.unit = '%'; - } else { - parse_result.type = 'unit'; - parse_result.prefix = ''; - parse_result.id = ean; + function is_number(char){ + n = char.charCodeAt(0); + return n >= 48 && n <= 57; } + + function match_pattern(ean,pattern){ + for(var i = 0; i < pattern.length; i++){ + var p = pattern[i]; + var e = ean[i]; + if( is_number(p) && p !== e ){ + return false; + } + } + return true; + } + + function get_value(ean,pattern){ + var value = 0; + var decimals = 0; + for(var i = 0; i < pattern.length; i++){ + var p = pattern[i]; + var v = parseInt(ean[i]); + if( p === 'N'){ + value *= 10; + value += v; + }else if( p === 'D'){ + decimals += 1; + value += v * Math.pow(10,-decimals); + } + } + return value; + } + + function get_basecode(ean,pattern){ + var base = ''; + for(var i = 0; i < pattern.length; i++){ + var p = pattern[i]; + var v = ean[i]; + if( p === 'x' || is_number(p)){ + base += v; + }else{ + base += '0'; + } + } + return self.sanitize_ean(base); + } + + var patterns = this.sorted_patterns; + + for(var i = 0; i < patterns.length; i++){ + if(match_pattern(ean,patterns[i].pattern)){ + parse_result.type = patterns[i].type; + parse_result.value = get_value(ean,patterns[i].pattern); + parse_result.base_code = get_basecode(ean,patterns[i].pattern); + return parse_result; + } + } + return parse_result; }, @@ -702,21 +674,18 @@ function openerp_pos_devices(instance,module){ //module is instance.point_of_sal }else if(this.pos.db.get_product_by_reference(code)){ var parse_result = { encoding: 'reference', - type: 'unit', + type: 'product', code: code, - prefix: '', }; }else{ var parse_result = { encoding: 'error', type: 'error', code: code, - prefix: '', }; - return; } - if(parse_result.type in {'unit':'', 'weight':'', 'price':''}){ //ean is associated to a product + if(parse_result.type in {'product':'', 'weight':'', 'price':'', 'discount':''}){ //ean is associated to a product if(this.action_callback['product']){ this.action_callback['product'](parse_result); } diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 94a23042df6..92276db60de 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -24,7 +24,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.pos_widget = attributes.pos_widget; this.proxy = new module.ProxyDevice(this); // used to communicate to the hardware devices via a local proxy - this.barcode_reader = new module.BarcodeReader({'pos': this, proxy:this.proxy}); // used to read barcodes + this.barcode_reader = new module.BarcodeReader({'pos': this, proxy:this.proxy, patterns: {}}); // used to read barcodes this.proxy_queue = new module.JobQueue(); // used to prevent parallels communications to the proxy this.db = new module.PosDB(); // a local database used to search trough products and categories & store pending orders this.debug = jQuery.deparam(jQuery.param.querystring()).debug !== undefined; //debug mode @@ -34,6 +34,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.company_logo = null; this.company_logo_base64 = ''; this.currency = null; + this.shop = null; this.company = null; this.user = null; this.users = []; @@ -47,6 +48,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.units = []; this.units_by_id = {}; this.pricelist = null; + this.order_sequence = 1; window.posmodel = this; // these dynamic attributes can be watched for change by other models or widgets @@ -118,6 +120,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.pos_widget.loading_message(_t('Loading')+' '+model,this._load_progress); return new instance.web.Model(model).query(fields).filter(domain).context(ctx).all() }, + // loads all the needed data on the sever. returns a deferred indicating when all the data has loaded. load_server_data: function(){ var self = this; @@ -148,6 +151,8 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal var units_by_id = {}; for(var i = 0, len = units.length; i < len; i++){ units_by_id[units[i].id] = units[i]; + units[i].groupable = ( units[i].category_id[0] === 1 ); + units[i].is_unit = ( units[i].id === 1 ); } self.units_by_id = units_by_id; @@ -155,9 +160,10 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }).then(function(users){ self.users = users; - return self.fetch('res.partner', ['name','ean13'], [['ean13', '!=', false]]); + return self.fetch('res.partner', ['name','street','city','country_id','phone','zip','mobile','email','ean13']); }).then(function(partners){ self.partners = partners; + self.db.add_partners(partners); return self.fetch('account.tax', ['name','amount', 'price_include', 'type']); }).then(function(taxes){ @@ -165,23 +171,13 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal return self.fetch( 'pos.session', - ['id', 'journal_ids','name','user_id','config_id','start_at','stop_at'], + ['id', 'journal_ids','name','user_id','config_id','start_at','stop_at','sequence_number'], [['state', '=', 'opened'], ['user_id', '=', self.session.uid]] ); }).then(function(pos_sessions){ self.pos_session = pos_sessions[0]; - return self.fetch( - 'pos.config', - ['name','journal_ids','journal_id','pricelist_id', - 'iface_self_checkout', 'iface_led', 'iface_cashdrawer', - 'iface_payment_terminal', 'iface_electronic_scale', 'iface_barscan', - 'iface_vkeyboard','iface_print_via_proxy','iface_scan_via_proxy', - 'iface_cashdrawer','iface_invoicing','iface_big_scrollbars', - 'receipt_header','receipt_footer','proxy_ip', - 'state','sequence_id','session_ids'], - [['id','=', self.pos_session.config_id[0]]] - ); + return self.fetch('pos.config',[],[['id','=', self.pos_session.config_id[0]]]); }).then(function(configs){ self.config = configs[0]; self.config.use_proxy = self.config.iface_payment_terminal || @@ -189,6 +185,18 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal self.config.iface_print_via_proxy || self.config.iface_scan_via_proxy || self.config.iface_cashdrawer; + + self.barcode_reader.add_barcode_patterns({ + 'product': self.config.barcode_product, + 'cashier': self.config.barcode_cashier, + 'client': self.config.barcode_customer, + 'weight': self.config.barcode_weight, + 'discount': self.config.barcode_discount, + 'price': self.config.barcode_price, + }); + return self.fetch('stock.location',[],[['id','=', self.config.stock_location_id[0]]]); + }).then(function(shops){ + self.shop = shops[0]; return self.fetch('product.pricelist',['currency_id'],[['id','=',self.config.pricelist_id[0]]]); }).then(function(pricelists){ @@ -198,12 +206,6 @@ 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_tmpl_id']); }).then(function(packagings){ self.db.add_packagings(packagings); @@ -292,7 +294,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal // this is called when an order is removed from the order collection. It ensures that there is always an existing // order and a valid selected order on_removed_order: function(removed_order,index,reason){ - if(reason === 'abandon' && this.get('orders').size() > 0){ + if( (reason === 'abandon' || removed_order.temporary) && this.get('orders').size() > 0){ // when we intentionally remove an unfinished order, and there is another existing one this.set({'selectedOrder' : this.get('orders').at(index) || this.get('orders').last()}); }else{ @@ -309,6 +311,10 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.set('selectedOrder', order); }, + get_order: function(){ + return this.get('selectedOrder'); + }, + //removes the current order delete_current_order: function(){ this.get('selectedOrder').destroy({'reason':'abandon'}); @@ -497,6 +503,8 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal selectedOrder.addProduct(product, {price:parsed_code.value}); }else if(parsed_code.type === 'weight'){ selectedOrder.addProduct(product, {quantity:parsed_code.value, merge:false}); + }else if(parsed_code.type === 'discount'){ + selectedOrder.addProduct(product, {discount:parsed_code.value, merge:false}); }else{ selectedOrder.addProduct(product); } @@ -504,6 +512,8 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }, }); + var orderline_id = 1; + // An orderline represent one element of the content of a client's shopping cart. // An orderline contains a product, its quantity, its price, discount. etc. // An Order contains zero or more Orderlines. @@ -519,6 +529,21 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.discountStr = '0'; this.type = 'unit'; this.selected = false; + this.id = orderline_id++; + }, + clone: function(){ + var orderline = new module.Orderline({},{ + pos: this.pos, + order: null, + product: this.product, + price: this.price, + }); + orderline.quantity = this.quantity; + orderline.quantityStr = this.quantityStr; + orderline.discount = this.discount; + orderline.type = this.type; + orderline.selected = false; + return orderline; }, // sets a discount [0,100]% set_discount: function(discount){ @@ -566,7 +591,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal }, get_quantity_str_with_unit: function(){ var unit = this.get_unit(); - if(unit && unit.name !== 'Unit(s)'){ + if(unit && !unit.is_unit){ return this.quantityStr + ' ' + unit.name; }else{ return this.quantityStr; @@ -602,6 +627,8 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal can_be_merged_with: function(orderline){ if( this.get_product().id !== orderline.get_product().id){ //only orderline of the same product can be merged return false; + }else if(!this.get_unit() || !this.get_unit().groupable){ + return false; }else if(this.get_product_type() !== orderline.get_product_type()){ return false; }else if(this.get_discount() > 0){ // we don't merge discounted orderlines @@ -787,11 +814,24 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.selected_paymentline = undefined; this.screen_data = {}; // see ScreenSelector this.receipt_type = 'receipt'; // 'receipt' || 'invoice' + this.temporary = attributes.temporary || false; + this.sequence_number = this.pos.pos_session.sequence_number++; return this; }, + is_empty: function(){ + return (this.get('orderLines').models.length === 0); + }, generateUniqueId: function() { return new Date().getTime(); }, + addOrderline: function(line){ + if(line.order){ + order.removeOrderline(line); + } + line.order = this; + this.get('orderLines').add(line); + this.selectLine(this.getLastOrderline()); + }, addProduct: function(product, options){ options = options || {}; var attr = JSON.parse(JSON.stringify(product)); @@ -805,6 +845,9 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal if(options.price !== undefined){ line.set_unit_price(options.price); } + if(options.discount !== undefined){ + line.set_discount(options.discount); + } var last_orderline = this.getLastOrderline(); if( last_orderline && last_orderline.can_be_merged_with(line) && options.merge !== false){ @@ -818,6 +861,15 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal this.get('orderLines').remove(line); this.selectLine(this.getLastOrderline()); }, + getOrderline: function(id){ + var orderlines = this.get('orderLines').models; + for(var i = 0; i < orderlines.length; i++){ + if(orderlines[i].id === id){ + return orderlines[i]; + } + } + return null; + }, getLastOrderline: function(){ return this.get('orderLines').at(this.get('orderLines').length -1); }, @@ -950,6 +1002,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal var client = this.get('client'); var cashier = this.pos.cashier || this.pos.user; var company = this.pos.company; + var shop = this.pos.shop; var date = new Date(); return { @@ -994,6 +1047,9 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal phone: company.phone, logo: this.pos.company_logo_base64, }, + shop:{ + name: shop.name, + }, currency: this.pos.currency, }; }, @@ -1019,6 +1075,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal partner_id: this.get_client() ? this.get_client().id : false, user_id: this.pos.cashier ? this.pos.cashier.id : this.pos.user.id, uid: this.uid, + sequence_number: this.sequence_number, }; }, getSelectedLine: function(){ @@ -1102,7 +1159,6 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal } }, switchSign: function() { - console.log('switchsing'); var oldBuffer; oldBuffer = this.get('buffer'); this.set({ diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index 71fdb3a5609..2f2f5e57312 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -29,12 +29,11 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa this.popup_set = options.popup_set || {}; - this.default_client_screen = options.default_client_screen; - this.default_cashier_screen = options.default_cashier_screen; + this.default_screen = options.default_screen; this.current_popup = null; - this.current_mode = options.default_mode || 'client'; + this.current_mode = options.default_mode || 'cashier'; this.current_screen = null; @@ -46,10 +45,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa this.popup_set[popup_name].hide(); } - this.selected_order = this.pos.get('selectedOrder'); - this.selected_order.set_screen_data({ - client_screen: this.default_client_screen, - cashier_screen: this.default_cashier_screen, + this.pos.get('selectedOrder').set_screen_data({ + 'screen': this.default_screen, }); this.pos.bind('change:selectedOrder', this.load_saved_screen, this); @@ -59,12 +56,12 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa this.screen_set[screen_name] = screen; return this; }, - show_popup: function(name){ + show_popup: function(name,options){ if(this.current_popup){ this.close_popup(); } this.current_popup = this.popup_set[name]; - this.current_popup.show(); + this.current_popup.show(options); }, close_popup: function(){ if(this.current_popup){ @@ -75,15 +72,10 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa }, load_saved_screen: function(){ this.close_popup(); - var selectedOrder = this.pos.get('selectedOrder'); + // this.set_current_screen(selectedOrder.get_screen_data('screen') || this.default_screen,null,'refresh'); + this.set_current_screen(this.default_screen,null,'refresh'); - if(this.current_mode === 'client'){ - this.set_current_screen(selectedOrder.get_screen_data('client_screen') || this.default_client_screen,null,'refresh'); - }else if(this.current_mode === 'cashier'){ - this.set_current_screen(selectedOrder.get_screen_data('cashier_screen') || this.default_cashier_screen,null,'refresh'); - } - this.selected_order = selectedOrder; }, set_user_mode: function(user_mode){ if(user_mode !== this.current_mode){ @@ -102,20 +94,21 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa } this.close_popup(); - var selectedOrder = this.pos.get('selectedOrder'); - if(this.current_mode === 'client'){ - selectedOrder.set_screen_data('client_screen',screen_name); - if(params){ - selectedOrder.set_screen_data('client_screen_params',params); - } - }else{ - selectedOrder.set_screen_data('cashier_screen',screen_name); - if(params){ - selectedOrder.set_screen_data('cashier_screen_params',params); - } + + var order = this.pos.get('selectedOrder'); + var old_screen_name = order.get_screen_data('screen'); + + order.set_screen_data('screen',screen_name); + + if(params){ + order.set_screen_data('params',params); } - if(screen && (refresh || screen !== this.current_screen)){ + if( screen_name !== old_screen_name ){ + order.set_screen_data('previous-screen',old_screen_name); + } + + if ( refresh || screen !== this.current_screen){ if(this.current_screen){ this.current_screen.close(); this.current_screen.hide(); @@ -124,21 +117,21 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa this.current_screen.show(); } }, - get_current_screen_param: function(param){ - var selected_order = this.pos.get('selectedOrder'); - if(this.current_mode === 'client'){ - var params = selected_order.get_screen_data('client_screen_params'); - }else{ - var params = selected_order.get_screen_data('cashier_screen_params'); - } - if(params){ - return params[param]; - }else{ - return undefined; + get_current_screen: function(){ + return this.pos.get('selectedOrder').get_screen_data('screen') || this.default_screen; + }, + back: function(){ + var previous = this.pos.get('selectedOrder').get_screen_data('previous-screen'); + if(previous){ + this.set_current_screen(previous); } }, + get_current_screen_param: function(param){ + var params = this.pos.get('selectedOrder').get_screen_data('params'); + return params ? params[param] : undefined; + }, set_default_screen: function(){ - this.set_current_screen(this.current_mode === 'client' ? this.default_client_screen : this.default_cashier_screen); + this.set_current_screen(this.default_screen); }, }); @@ -157,25 +150,19 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa }, barcode_product_screen: 'products', //if defined, this screen will be loaded when a product is scanned - barcode_product_error_popup: 'error-product', //if defined, this popup will be loaded when there's an error in the popup hotkeys_handlers: {}, // what happens when a product is scanned : - // it will add the product to the order and go to barcode_product_screen. Or show barcode_product_error_popup if - // there's an error. + // it will add the product to the order and go to barcode_product_screen. barcode_product_action: function(code){ var self = this; if(self.pos.scan_product(code)){ - self.pos.proxy.scan_item_success(code); if(self.barcode_product_screen){ self.pos_widget.screen_selector.set_current_screen(self.barcode_product_screen); } }else{ - self.pos.proxy.scan_item_error_unrecognized(code); - if(self.barcode_product_error_popup && self.pos_widget.screen_selector.get_user_mode() !== 'cashier'){ - self.pos_widget.screen_selector.show_popup(self.barcode_product_error_popup); - } + self.pos_widget.screen_selector.show_popup('error-barcode',code.code); } }, @@ -189,12 +176,10 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa if(users[i].ean13 === code.code){ this.pos.cashier = users[i]; this.pos_widget.username.refresh(); - this.pos.proxy.cashier_mode_activated(); - this.pos_widget.screen_selector.set_user_mode('cashier'); return true; } } - this.pos.proxy.scan_item_error_unrecognized(code); + this.pos_widget.screen_selector.show_popup('error-barcode',code.code); return false; }, @@ -203,30 +188,28 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa // - if there's a user with a matching ean, put it as the active 'client' and return true // - else : return false. barcode_client_action: function(code){ - var partners = this.pos.partners; - for(var i = 0, len = partners.length; i < len; i++){ - if(partners[i].ean13 === code.code){ - this.pos.get('selectedOrder').set_client(partners[i]); - this.pos_widget.username.refresh(); - this.pos.proxy.scan_item_success(code); - return true; - } + var partner = this.pos.db.get_partner_by_ean13(code.code); + if(partner){ + this.pos.get('selectedOrder').set_client(partner); + this.pos_widget.username.refresh(); + return true; } - this.pos.proxy.scan_item_error_unrecognized(code); + this.pos_widget.screen_selector.show_popup('error-barcode',code.code); return false; - //TODO start the transaction }, // what happens when a discount barcode is scanned : the default behavior // is to set the discount on the last order. barcode_discount_action: function(code){ - this.pos.proxy.scan_item_success(code); var last_orderline = this.pos.get('selectedOrder').getLastOrderline(); if(last_orderline){ last_orderline.set_discount(code.value) } }, - + // What happens when an invalid barcode is scanned : shows an error popup. + barcode_error_action: function(code){ + this.pos_widget.screen_selector.show_popup('error-barcode',code.code); + }, // shows an action bar on the screen. The actionbar is automatically shown when you add a button // with add_action_button() show_action_bar: function(){ @@ -264,33 +247,11 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa this.hide_action_bar(); } - // we add the help button by default. we do this because the buttons are cleared on each refresh so that - // the button stay local to each screen - this.pos_widget.left_action_bar.add_new_button({ - label: _t('Help'), - icon: '/point_of_sale/static/src/img/icons/png48/help.png', - click: function(){ self.help_button_action(); }, - }); - var self = this; - this.cashier_mode = this.pos_widget.screen_selector.get_user_mode() === 'cashier'; - this.pos_widget.set_numpad_visible(this.show_numpad && this.cashier_mode); + this.pos_widget.set_numpad_visible(this.show_numpad); this.pos_widget.set_leftpane_visible(this.show_leftpane); - this.pos_widget.set_left_action_bar_visible(this.show_leftpane && !this.cashier_mode); - this.pos_widget.set_cashier_controls_visible(this.cashier_mode); - if(this.cashier_mode && this.pos.config.iface_self_checkout){ - this.pos_widget.client_button.show(); - }else{ - this.pos_widget.client_button.hide(); - } - if(this.cashier_mode){ - this.pos_widget.close_button.show(); - }else{ - this.pos_widget.close_button.hide(); - } - this.pos_widget.username.set_user_mode(this.pos_widget.screen_selector.get_user_mode()); this.pos.barcode_reader.set_action_callback({ @@ -298,6 +259,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa 'product': self.barcode_product_action ? function(code){ self.barcode_product_action(code); } : undefined , 'client' : self.barcode_client_action ? function(code){ self.barcode_client_action(code); } : undefined , 'discount': self.barcode_discount_action ? function(code){ self.barcode_discount_action(code); } : undefined, + 'error' : self.barcode_error_action ? function(code){ self.barcode_error_action(code); } : undefined, }); }, @@ -308,7 +270,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa this.pos.barcode_reader.reset_action_callbacks(); } this.pos_widget.action_bar.destroy_buttons(); - this.pos_widget.left_action_bar.destroy_buttons(); }, // this methods hides the screen. It's not a good place to put your cleanup stuff as it is called on the @@ -352,21 +313,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa }, }); - module.HelpPopupWidget = module.PopUpWidget.extend({ - template:'HelpPopupWidget', - show: function(){ - this._super(); - this.pos.proxy.help_needed(); - var self = this; - - this.$el.find('.button').off('click').click(function(){ - self.pos_widget.screen_selector.close_popup(); - }); - }, - close:function(){ - this.pos.proxy.help_canceled(); - }, - }); module.ChooseReceiptPopupWidget = module.PopUpWidget.extend({ template:'ChooseReceiptPopupWidget', @@ -398,42 +344,64 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa module.ErrorPopupWidget = module.PopUpWidget.extend({ template:'ErrorPopupWidget', - show: function(){ + show: function(text){ var self = this; this._super(); - this.pos.proxy.help_needed(); - this.pos.proxy.scan_item_error_unrecognized(); + + if( text && (text.message || text.comment) ){ + this.$('.message').text(text.message); + this.$('.comment').text(text.comment); + } this.pos.barcode_reader.save_callbacks(); this.pos.barcode_reader.reset_action_callbacks(); - this.pos.barcode_reader.set_action_callback({ - 'cashier': function(code){ - clearInterval(this.intervalID); - self.pos.proxy.cashier_mode_activated(); - self.pos_widget.screen_selector.set_user_mode('cashier'); - }, - }); this.$('.footer .button').off('click').click(function(){ self.pos_widget.screen_selector.close_popup(); }); }, close:function(){ this._super(); - this.pos.proxy.help_canceled(); this.pos.barcode_reader.restore_callbacks(); }, }); - module.ProductErrorPopupWidget = module.ErrorPopupWidget.extend({ - template:'ProductErrorPopupWidget', - }); module.ErrorSessionPopupWidget = module.ErrorPopupWidget.extend({ template:'ErrorSessionPopupWidget', }); - module.ErrorNegativePricePopupWidget = module.ErrorPopupWidget.extend({ - template:'ErrorNegativePricePopupWidget', + module.ErrorBarcodePopupWidget = module.ErrorPopupWidget.extend({ + template:'ErrorBarcodePopupWidget', + show: function(barcode){ + this._super(); + this.$('.barcode').text(barcode); + }, + }); + + module.ConfirmPopupWidget = module.PopUpWidget.extend({ + template: 'ConfirmPopupWidget', + show: function(options){ + var self = this; + this._super(); + + this.message = options.message || ''; + this.comment = options.comment || ''; + this.renderElement(); + + this.$('.button.cancel').click(function(){ + self.pos_widget.screen_selector.close_popup(); + if( options.cancel ){ + options.cancel.call(self); + } + }); + + this.$('.button.confirm').click(function(){ + self.pos_widget.screen_selector.close_popup(); + if( options.confirm ){ + options.confirm.call(self); + } + }); + }, }); module.ErrorNoClientPopupWidget = module.ErrorPopupWidget.extend({ @@ -443,47 +411,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa module.ErrorInvoiceTransferPopupWidget = module.ErrorPopupWidget.extend({ template: 'ErrorInvoiceTransferPopupWidget', }); - - module.ScaleInviteScreenWidget = module.ScreenWidget.extend({ - template:'ScaleInviteScreenWidget', - - next_screen:'scale', - previous_screen:'products', - - show: function(){ - this._super(); - var self = this; - var queue = this.pos.proxy_queue; - - queue.schedule(function(){ - return self.pos.proxy.weighting_start(); - },{ important: true }); - - queue.schedule(function(){ - return self.pos.proxy.weighting_read_kg().then(function(weight){ - if(weight > 0.001){ - self.pos_widget.screen_selector.set_current_screen(self.next_screen); - } - }); - },{duration: 100, repeat: true}); - - this.add_action_button({ - label: _t('Back'), - icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png', - click: function(){ - self.pos_widget.screen_selector.set_current_screen(self.previous_screen); - } - }); - }, - close: function(){ - this._super(); - var self = this; - this.pos.proxy_queue.clear(); - this.pos.proxy_queue.schedule(function(){ - return self.pos.proxy.weighting_end(); - },{ important: true }); - }, - }); module.ScaleScreenWidget = module.ScreenWidget.extend({ template:'ScaleScreenWidget', @@ -491,6 +418,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa next_screen: 'products', previous_screen: 'products', + show_leftpane: false, + show: function(){ this._super(); var self = this; @@ -510,23 +439,15 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa $('body').on('keyup',this.hotkey_handler); - this.add_action_button({ - label: _t('Back'), - icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png', - click: function(){ - self.pos_widget.screen_selector.set_current_screen(self.previous_screen); - } - }); + this.$('.back').click(function(){ + self.pos_widget.screen_selector.set_current_screen(self.previous_screen); + }); + + this.$('.next,.buy-product').click(function(){ + self.order_product(); + self.pos_widget.screen_selector.set_current_screen(self.next_screen); + }); - this.validate_button = this.add_action_button({ - label: _t('Validate'), - icon: '/point_of_sale/static/src/img/icons/png48/validate.png', - click: function(){ - self.order_product(); - self.pos_widget.screen_selector.set_current_screen(self.next_screen); - }, - }); - queue.schedule(function(){ return self.pos.proxy.scale_read().then(function(weight){ self.set_weight(weight.weight); @@ -534,14 +455,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa },{duration:50, repeat: true}); }, - renderElement: function(){ - var self = this; - this._super(); - this.$('.product-picture').click(function(){ - self.order_product(); - self.pos_widget.screen_selector.set_current_screen(self.next_screen); - }); - }, get_product: function(){ var ss = this.pos_widget.screen_selector; if(ss){ @@ -563,7 +476,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa }, set_weight: function(weight){ this.weight = weight; - this.$('.js-weight').text(this.get_product_weight_string()); + this.$('.weight').text(this.get_product_weight_string()); + this.$('.computed-price').text(this.get_computed_price_string()); }, get_product_weight_string: function(){ var product = this.get_product(); @@ -581,13 +495,8 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa weightstr += ' Kg'; return weightstr; }, - get_product_image_url: function(){ - var product = this.get_product(); - if(product){ - return window.location.origin + '/web/binary/image?model=product.product&field=image_medium&id='+product.id; - }else{ - return ""; - } + get_computed_price_string: function(){ + return this.format_currency(this.get_product_price() * this.weight); }, close: function(){ var self = this; @@ -598,166 +507,9 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa }, }); - - module.ClientPaymentScreenWidget = module.ScreenWidget.extend({ - template:'ClientPaymentScreenWidget', - - next_screen: 'welcome', - previous_screen: 'products', - - show: function(){ - this._super(); - var self = this; - - this.queue = new module.JobQueue(); - this.canceled = false; - this.paid = false; - - // initiates the connection to the payment terminal and starts the update requests - this.start = function(){ - var def = new $.Deferred(); - self.pos.proxy.payment_request(self.pos.get('selectedOrder').getDueLeft()) - .done(function(ack){ - if(ack === 'ok'){ - self.queue.schedule(self.update); - }else if(ack.indexOf('error') === 0){ - console.error('cannot make payment. TODO'); - }else{ - console.error('unknown payment request return value:',ack); - } - def.resolve(); - }); - return def; - }; - - // gets updated status from the payment terminal and performs the appropriate consequences - this.update = function(){ - var def = new $.Deferred(); - if(self.canceled){ - return def.resolve(); - } - self.pos.proxy.payment_status() - .done(function(status){ - if(status.status === 'paid'){ - - var currentOrder = self.pos.get('selectedOrder'); - - //we get the first cashregister marked as self-checkout - var selfCheckoutRegisters = []; - for(var i = 0; i < self.pos.cashregisters.length; i++){ - var cashregister = self.pos.cashregisters[i]; - if(cashregister.self_checkout_payment_method){ - selfCheckoutRegisters.push(cashregister); - } - } - - var cashregister = selfCheckoutRegisters[0] || self.pos.cashregisters[0]; - currentOrder.addPaymentline(cashregister); - self.pos.push_order(currentOrder) - currentOrder.destroy(); - self.pos.proxy.transaction_end(); - self.pos_widget.screen_selector.set_current_screen(self.next_screen); - self.paid = true; - }else if(status.status.indexOf('error') === 0){ - console.error('error in payment request. TODO'); - }else if(status.status === 'waiting'){ - self.queue.schedule(self.update,200); - }else{ - console.error('unknown status value:',status.status); - } - def.resolve(); - }); - return def; - } - - // cancels a payment. - this.cancel = function(){ - if(!self.paid && !self.canceled){ - self.canceled = true; - self.pos.proxy.payment_cancel(); - self.pos_widget.screen_selector.set_current_screen(self.previous_screen); - self.queue.clear(); - } - return (new $.Deferred()).resolve(); - } - - if(this.pos.get('selectedOrder').getDueLeft() <= 0){ - this.pos_widget.screen_selector.show_popup('error-negative-price'); - }else{ - this.queue.schedule(this.start); - } - - this.add_action_button({ - label: _t('Back'), - icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png', - click: function(){ - self.queue.schedule(self.cancel); - self.pos_widget.screen_selector.set_current_screen(self.previous_screen); - } - }); - }, - close: function(){ - if(this.queue){ - this.queue.schedule(this.cancel); - } - //TODO CANCEL - this._super(); - }, - }); - - module.WelcomeScreenWidget = module.ScreenWidget.extend({ - template:'WelcomeScreenWidget', - - next_screen: 'products', - - show_numpad: false, - show_leftpane: false, - start: function(){ - this._super(); - $('.goodbye-message').click(function(){ - $(this).addClass('oe_hidden'); - }); - }, - - barcode_product_action: function(code){ - this.pos.proxy.transaction_start(); - this._super(code); - }, - - barcode_client_action: function(code){ - this.pos.proxy.transaction_start(); - this._super(code); - $('.goodbye-message').addClass('oe_hidden'); - this.pos_widget.screen_selector.show_popup('choose-receipt'); - }, - - show: function(){ - this._super(); - var self = this; - - this.add_action_button({ - label: _t('Help'), - icon: '/point_of_sale/static/src/img/icons/png48/help.png', - click: function(){ - $('.goodbye-message').css({opacity:1}).addClass('oe_hidden'); - self.help_button_action(); - }, - }); - - $('.goodbye-message').css({opacity:1}).removeClass('oe_hidden'); - setTimeout(function(){ - $('.goodbye-message').animate({opacity:0},500,'swing',function(){$('.goodbye-message').addClass('oe_hidden');}); - },5000); - }, - }); - module.ProductScreenWidget = module.ScreenWidget.extend({ template:'ProductScreenWidget', - scale_screen: 'scale', - client_scale_screen : 'scale_invite', - client_next_screen: 'client_payment', - show_numpad: true, show_leftpane: true, @@ -767,19 +519,19 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa this.product_list_widget = new module.ProductListWidget(this,{ click_product_action: function(product){ if(product.to_weight && self.pos.config.iface_electronic_scale){ - self.pos_widget.screen_selector.set_current_screen( self.cashier_mode ? self.scale_screen : self.client_scale_screen, {product: product}); + self.pos_widget.screen_selector.set_current_screen('scale',{product: product}); }else{ self.pos.get('selectedOrder').addProduct(product); } }, product_list: this.pos.db.get_product_by_category(0) }); - this.product_list_widget.replace($('.placeholder-ProductListWidget')); + this.product_list_widget.replace(this.$('.placeholder-ProductListWidget')); this.product_categories_widget = new module.ProductCategoriesWidget(this,{ product_list_widget: this.product_list_widget, }); - this.product_categories_widget.replace($('.placeholder-ProductCategoriesWidget')); + this.product_categories_widget.replace(this.$('.placeholder-ProductCategoriesWidget')); }, show: function(){ @@ -789,16 +541,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa this.product_categories_widget.reset_category(); this.pos_widget.order_widget.set_editable(true); - - if(this.pos_widget.screen_selector.current_mode === 'client'){ - this.add_action_button({ - label: _t('Pay'), - icon: '/point_of_sale/static/src/img/icons/png48/go-next.png', - click: function(){ - self.pos_widget.screen_selector.set_current_screen(self.client_next_screen); - } - }); - } }, close: function(){ @@ -812,6 +554,180 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa }, }); + module.ClientListScreenWidget = module.ScreenWidget.extend({ + template: 'ClientListScreenWidget', + + show_leftpane: false, + + auto_back: true, + + show: function(){ + var self = this; + this._super(); + + this.renderElement(); + this.details_visible = false; + this.old_client = this.pos.get('selectedOrder').get('client'); + this.new_client = this.old_client; + + this.$('.back').click(function(){ + self.pos_widget.screen_selector.back(); + }); + + this.$('.next').click(function(){ + self.save_changes(); + self.pos_widget.screen_selector.back(); + }); + + var partners = this.pos.db.get_partners_sorted(); + this.render_list(partners); + + if( this.old_client ){ + this.display_client_details('show',this.old_client,0); + } + + this.$('.client-list-contents').delegate('.client-line','click',function(event){ + console.log('uh'); + self.line_select(event,$(this),parseInt($(this).data('id'))); + }); + + var search_timeout = null; + this.$('.searchbox input').on('keyup',function(event){ + clearTimeout(search_timeout); + + var query = this.value; + + search_timeout = setTimeout(function(){ + self.perform_search(query,event.which === 13); + },70); + }); + + this.$('.searchbox .search-clear').click(function(){ + self.clear_search(); + }); + }, + perform_search: function(query, associate_result){ + if(query){ + var customers = this.pos.db.search_partner(query); + this.display_client_details('hide'); + if ( associate_result && customers.length === 1){ + this.new_client = customers[0]; + this.save_changes(); + this.pos_widget.screen_selector.back(); + } + this.render_list(customers); + }else{ + var customers = this.pos.db.get_partners_sorted(); + this.render_list(customers); + } + }, + clear_search: function(){ + var customers = this.pos.db.get_partners_sorted(); + this.render_list(customers); + this.$('.searchbox input')[0].value = ''; + this.$('.searchbox input').focus(); + }, + render_list: function(partners){ + var contents = this.$('.client-list-contents'); + contents.empty(); + for(var i = 0, len = partners.length; i < len; i++){ + var clientline = $(QWeb.render('ClientLine',{partner:partners[i]})); + if( partners[i] === this.new_client ){ + clientline.addClass('highlight'); + } + contents.append(clientline); + } + }, + save_changes: function(){ + if( this.has_client_changed() ){ + this.pos.get('selectedOrder').set_client(this.new_client); + } + }, + has_client_changed: function(){ + if( this.old_client && this.new_client ){ + return this.old_client.id !== this.new_client.id; + }else{ + return !!this.old_client !== !!this.new_client; + } + }, + toggle_save_button: function(){ + var $button = this.$('.button.next'); + if( this.new_client ){ + if( !this.old_client){ + $button.text(_t('Set Customer')); + }else{ + $button.text(_t('Change Customer')); + } + }else{ + $button.text(_t('Deselect Customer')); + } + $button.toggleClass('oe_hidden',!this.has_client_changed()); + }, + line_select: function(event,$line,id){ + var partner = this.pos.db.get_partner_by_id(id); + this.$('.client-list .lowlight').removeClass('lowlight'); + if ( $line.hasClass('highlight') ){ + $line.removeClass('highlight'); + $line.addClass('lowlight'); + this.display_client_details('hide',partner); + this.new_client = null; + this.toggle_save_button(); + }else{ + this.$('.client-list .highlight').removeClass('highlight'); + $line.addClass('highlight'); + var y = event.pageY - $line.parent().offset().top + this.display_client_details('show',partner,y); + this.new_client = partner; + this.toggle_save_button(); + } + }, + partner_icon_url: function(id){ + return '/web/binary/image?model=res.partner&id='+id+'&field=image_small'; + }, + display_client_details: function(visibility,partner,clickpos){ + if(visibility === 'show'){ + var contents = this.$('.client-details-contents'); + var parent = this.$('.client-list').parent(); + var old_scroll = parent.scrollTop(); + var old_height = contents.height(); + contents.empty(); + contents.append($(QWeb.render('ClientDetails',{widget:this,partner:partner}))); + var new_height = contents.height(); + + if(!this.details_visible){ + if(clickpos < old_scroll + new_height + 20 ){ + parent.scrollTop( clickpos - 20 ); + }else{ + parent.scrollTop(parent.scrollTop() + new_height); + } + }else{ + parent.scrollTop(parent.scrollTop() - old_height + new_height); + } + + this.details_visible = true; + }else if(visibility === 'hide'){ + var contents = this.$('.client-details-contents'); + var parent = this.$('.client-list').parent(); + var scroll = parent.scrollTop(); + var height = contents.height(); + contents.empty(); + if( height > scroll ){ + contents.css({height:height+'px'}); + contents.animate({height:0},400,function(){ + contents.css({height:''}); + }); + //parent.scrollTop(0); + }else{ + parent.scrollTop( parent.scrollTop() - height); + } + this.details_visible = false; + } + }, + close: function(){ + this._super(); + }, + }); + module.ReceiptScreenWidget = module.ScreenWidget.extend({ template: 'ReceiptScreenWidget', @@ -879,6 +795,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa } }); + module.PaymentScreenWidget = module.ScreenWidget.extend({ template: 'PaymentScreenWidget', back_screen: 'products', @@ -913,7 +830,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa if(node){ node.line.set_amount(this.value); } - }; this.line_click_handler = function(event){ @@ -943,8 +859,6 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa this.focus_selected_line(); document.body.addEventListener('keyup', this.hotkey_handler); - - this.add_action_button({ label: _t('Back'), @@ -1182,7 +1096,7 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa if(this.pos.config.iface_print_via_proxy){ var receipt = currentOrder.export_for_printing(); this.pos.proxy.print_receipt(QWeb.render('XmlReceipt',{ - receipt: receipt + receipt: receipt, widget: self, })); this.pos.get('selectedOrder').destroy(); //finish order and go back to scan screen }else{ diff --git a/addons/point_of_sale/static/src/js/widgets.js b/addons/point_of_sale/static/src/js/widgets.js index 95e07a17c0a..5db12165737 100644 --- a/addons/point_of_sale/static/src/js/widgets.js +++ b/addons/point_of_sale/static/src/js/widgets.js @@ -130,7 +130,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this._super(parent,options); this.editable = false; this.pos.bind('change:selectedOrder', this.change_selected_order, this); - this.bind_orderline_events(); this.line_click_handler = function(event){ if(!self.editable){ return; @@ -138,6 +137,10 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa self.pos.get('selectedOrder').selectLine(this.orderline); self.pos_widget.numpad.state.reset(); }; + this.client_change_handler = function(event){ + self.update_summary(); + } + this.bind_order_events(); }, enable_numpad: function(){ this.disable_numpad(); //ensure we don't register the callbacks twice @@ -177,11 +180,16 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa } }, change_selected_order: function() { - this.bind_orderline_events(); + this.bind_order_events(); this.renderElement(); }, - bind_orderline_events: function() { - var lines = this.pos.get('selectedOrder').get('orderLines'); + bind_order_events: function() { + + var order = this.pos.get('selectedOrder'); + order.unbind('change:client', this.client_change_handler); + order.bind('change:client', this.client_change_handler); + + var lines = order.get('orderLines'); lines.unbind(); lines.bind('add', function(){ this.numpad_state.reset(); @@ -233,10 +241,12 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa var orderlines = order.get('orderLines').models; var el_str = openerp.qweb.render('OrderWidget',{widget:this, order:order, orderlines:orderlines}); + var el_node = document.createElement('div'); el_node.innerHTML = _.str.trim(el_str); el_node = el_node.childNodes[0]; + var list_container = el_node.querySelector('.orderlines'); for(var i = 0, len = orderlines.length; i < len; i++){ var orderline = this.render_orderline(orderlines[i]); @@ -260,6 +270,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.el.querySelector('.summary .total > .value').textContent = this.format_currency(total); this.el.querySelector('.summary .total .subentry .value').textContent = this.format_currency(taxes); + }, }); @@ -275,12 +286,22 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.pos.bind('change:selectedOrder', this.renderElement,this ); }, renderElement:function(){ + this.selected = ( this.pos.get('selectedOrder') === this.order ) this._super(); var self = this; this.$el.click(function(){ - self.selectOrder(); + if( self.pos.get('selectedOrder') === self.order ){ + var ss = self.pos.pos_widget.screen_selector; + if(ss.get_current_screen() === 'clientlist'){ + ss.back(); + }else{ + ss.set_current_screen('clientlist'); + } + }else{ + self.selectOrder(); + } }); - if( this.order === this.pos.get('selectedOrder') ){ + if( this.selected){ this.$el.addClass('selected'); } }, @@ -687,14 +708,9 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa unknown_product: '9900000000004', }, events:[ - 'scan_item_success', - 'scan_item_error_unrecognized', - 'payment_request', 'open_cashbox', 'print_receipt', - 'print_pdf_invoice', 'scale_read', - 'payment_status', ], minimized: false, init: function(parent,options){ @@ -763,12 +779,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa } self.minimized = !self.minimized; }); - this.$('.button.accept_payment').click(function(){ - self.pos.proxy.debug_accept_payment(); - }); - this.$('.button.reject_payment').click(function(){ - self.pos.proxy.debug_reject_payment(); - }); this.$('.button.set_weight').click(function(){ var kg = Number(self.$('input.weight').val()); if(!isNaN(kg)){ @@ -782,15 +792,15 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.$('.button.custom_ean').click(function(){ var ean = self.pos.barcode_reader.sanitize_ean(self.$('input.ean').val() || '0'); self.$('input.ean').val(ean); - self.pos.barcode_reader.scan('ean13',ean); + self.pos.barcode_reader.scan(ean); }); this.$('.button.reference').click(function(){ - self.pos.barcode_reader.scan('reference',self.$('input.ean').val()); + self.pos.barcode_reader.scan(self.$('input.ean').val()); }); _.each(this.eans, function(ean, name){ self.$('.button.'+name).click(function(){ self.$('input.ean').val(ean); - self.pos.barcode_reader.scan('ean13',ean); + self.pos.barcode_reader.scan(ean); }); }); _.each(this.events, function(name){ @@ -799,18 +809,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa self.$('.event.'+name).animate({'background-color':'#1E1E1E'},2000); }); }); - self.pos.proxy.add_notification('help_needed',function(){ - self.$('.status.help_needed').addClass('on'); - }); - self.pos.proxy.add_notification('help_canceled',function(){ - self.$('.status.help_needed').removeClass('on'); - }); - self.pos.proxy.add_notification('transaction_start',function(){ - self.$('.status.transaction').addClass('on'); - }); - self.pos.proxy.add_notification('transaction_end',function(){ - self.$('.status.transaction').removeClass('on'); - }); }, }); @@ -919,7 +917,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.pos_widget = this; //So that pos_widget's childs have pos_widget set automatically this.numpad_visible = true; - this.left_action_bar_visible = true; this.leftpane_visible = true; this.leftpane_width = '440px'; this.cashier_controls_visible = true; @@ -963,7 +960,17 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa }); self.$('.deleteorder-button').click(function(){ - self.pos.delete_current_order(); + if( !self.pos.get('selectedOrder').is_empty() ){ + self.screen_selector.show_popup('confirm',{ + message: _t('Destroy Current Order ?'), + comment: _t('You will lose any data associated with the current order'), + confirm: function(){ + self.pos.delete_current_order(); + }, + }); + }else{ + self.pos.delete_current_order(); + } }); //when a new order is created, add an order button widget @@ -1048,28 +1055,20 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.payment_screen = new module.PaymentScreenWidget(this, {}); this.payment_screen.appendTo(this.$('.screens')); - this.welcome_screen = new module.WelcomeScreenWidget(this,{}); - this.welcome_screen.appendTo(this.$('.screens')); - - this.client_payment_screen = new module.ClientPaymentScreenWidget(this, {}); - this.client_payment_screen.appendTo(this.$('.screens')); - - this.scale_invite_screen = new module.ScaleInviteScreenWidget(this, {}); - this.scale_invite_screen.appendTo(this.$('.screens')); + this.clientlist_screen = new module.ClientListScreenWidget(this, {}); + this.clientlist_screen.appendTo(this.$('.screens')); this.scale_screen = new module.ScaleScreenWidget(this,{}); this.scale_screen.appendTo(this.$('.screens')); - // -------- Popups --------- - this.help_popup = new module.HelpPopupWidget(this, {}); - this.help_popup.appendTo(this.$el); + // -------- Popups --------- this.error_popup = new module.ErrorPopupWidget(this, {}); this.error_popup.appendTo(this.$el); - this.error_product_popup = new module.ProductErrorPopupWidget(this, {}); - this.error_product_popup.appendTo(this.$el); + this.error_barcode_popup = new module.ErrorBarcodePopupWidget(this, {}); + this.error_barcode_popup.appendTo(this.$el); this.error_session_popup = new module.ErrorSessionPopupWidget(this, {}); this.error_session_popup.appendTo(this.$el); @@ -1077,15 +1076,15 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.choose_receipt_popup = new module.ChooseReceiptPopupWidget(this, {}); this.choose_receipt_popup.appendTo(this.$el); - this.error_negative_price_popup = new module.ErrorNegativePricePopupWidget(this, {}); - this.error_negative_price_popup.appendTo(this.$el); - this.error_no_client_popup = new module.ErrorNoClientPopupWidget(this, {}); this.error_no_client_popup.appendTo(this.$el); this.error_invoice_transfer_popup = new module.ErrorInvoiceTransferPopupWidget(this, {}); this.error_invoice_transfer_popup.appendTo(this.$el); + this.confirm_popup = new module.ConfirmPopupWidget(this,{}); + this.confirm_popup.appendTo(this.$el); + // -------- Misc --------- this.close_button = new module.HeaderButtonWidget(this,{ @@ -1108,9 +1107,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa this.action_bar = new module.ActionBarWidget(this); this.action_bar.replace(this.$(".placeholder-RightActionBar")); - this.left_action_bar = new module.ActionBarWidget(this); - this.left_action_bar.replace(this.$('.placeholder-LeftActionBar')); - this.paypad = new module.PaypadWidget(this, {}); this.paypad.replace(this.$('.placeholder-PaypadWidget')); @@ -1125,13 +1121,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa }); this.onscreen_keyboard.replace(this.$('.placeholder-OnscreenKeyboardWidget')); - this.client_button = new module.HeaderButtonWidget(this,{ - label: _t('Self-Checkout'), - action: function(){ self.screen_selector.set_user_mode('client'); }, - }); - this.client_button.appendTo(this.$('.pos-rightheader')); - - // -------- Screen Selector --------- this.screen_selector = new module.ScreenSelector({ @@ -1139,25 +1128,21 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa screen_set:{ 'products': this.product_screen, 'payment' : this.payment_screen, - 'client_payment' : this.client_payment_screen, - 'scale_invite' : this.scale_invite_screen, 'scale': this.scale_screen, 'receipt' : this.receipt_screen, - 'welcome' : this.welcome_screen, + 'clientlist': this.clientlist_screen, }, popup_set:{ - 'help': this.help_popup, 'error': this.error_popup, - 'error-product': this.error_product_popup, + 'error-barcode': this.error_barcode_popup, 'error-session': this.error_session_popup, - 'error-negative-price': this.error_negative_price_popup, 'choose-receipt': this.choose_receipt_popup, 'error-no-client': this.error_no_client_popup, 'error-invoice-transfer': this.error_invoice_transfer_popup, + 'confirm': this.confirm_popup, }, - default_client_screen: 'welcome', - default_cashier_screen: 'products', - default_mode: this.pos.config.iface_self_checkout ? 'client' : 'cashier', + default_screen: 'products', + default_mode: 'cashier', }); if(this.pos.debug){ @@ -1178,7 +1163,6 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa if(visible !== this.numpad_visible){ this.numpad_visible = visible; if(visible){ - this.set_left_action_bar_visible(false); this.numpad.show(); this.paypad.show(); }else{ @@ -1187,39 +1171,16 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa } } }, - set_left_action_bar_visible: function(visible){ - if(visible !== this.left_action_bar_visible){ - this.left_action_bar_visible = visible; - if(visible){ - this.set_numpad_visible(false); - this.left_action_bar.show(); - }else{ - this.left_action_bar.hide(); - } - } - }, //shows or hide the leftpane (contains the list of orderlines, the numpad, the paypad, etc.) set_leftpane_visible: function(visible){ if(visible !== this.leftpane_visible){ this.leftpane_visible = visible; if(visible){ - this.$('.pos-leftpane').removeClass('oe_hidden').animate({'width':this.leftpane_width},500,'swing'); - this.$('.pos-rightpane').animate({'left':this.leftpane_width},500,'swing'); + this.$('.pos-leftpane').removeClass('oe_hidden'); + this.$('.rightpane').css({'left':this.leftpane_width}); }else{ - var leftpane = this.$('.pos-leftpane'); - leftpane.animate({'width':'0px'},500,'swing', function(){ leftpane.addClass('oe_hidden'); }); - this.$('.pos-rightpane').animate({'left':'0px'},500,'swing'); - } - } - }, - //shows or hide the controls in the PosWidget that are specific to the cashier ( Orders, close button, etc. ) - set_cashier_controls_visible: function(visible){ - if(visible !== this.cashier_controls_visible){ - this.cashier_controls_visible = visible; - if(visible){ - this.$('.pos-rightheader').removeClass('oe_hidden'); - }else{ - this.$('.pos-rightheader').addClass('oe_hidden'); + this.$('.pos-leftpane').addClass('oe_hidden'); + this.$('.rightpane').css({'left':'0px'}); } } }, diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 55722d1e5d7..929212cce8d 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -12,9 +12,9 @@
- - + +
@@ -35,6 +35,7 @@