diff --git a/addons/account/account_cash_statement.py b/addons/account/account_cash_statement.py index 16aa2bafb7d..f21bd7e9a36 100644 --- a/addons/account/account_cash_statement.py +++ b/addons/account/account_cash_statement.py @@ -235,8 +235,27 @@ class account_cash_statement(osv.osv): print "line: %r" % (line,) if line and len(line)==3 and line[2]: amount_total+= line[2]['pieces'] * line[2]['number_opening'] + vals.update(balance_start= amount_total) vals.update(balance_end_real=self._compute_balance_end_real(cr, uid, vals['journal_id'], context=context)) + + details = vals.get('details_ids') + if not details: + result = self.onchange_journal_id(cr, uid, None, vals['journal_id'], context=context) + vals['details_ids'] = [] + + for value in (result['value']['details_ids'] or []): + print "value: %r" % (value,) + nested_values = { + 'number_closing' : False, + 'number_opening' : False, + 'pieces' : value['pieces'], + 'subtotal_closing' : False, + 'subtotal_opening' : False, + } + + vals['details_ids'].append([0, False, nested_values]) + return super(account_cash_statement, self).create(cr, uid, vals, context=context) def write(self, cr, uid, ids, vals, context=None): @@ -285,8 +304,6 @@ class account_cash_statement(osv.osv): for line in journal.cashbox_line_ids: values['value']['details_ids'].append({'pieces' : line.pieces}) - print "values: %r" % (values,) - return values else: return { diff --git a/addons/account/res_currency.py b/addons/account/res_currency.py index 8c9c6a8b103..8e02836274a 100644 --- a/addons/account/res_currency.py +++ b/addons/account/res_currency.py @@ -44,4 +44,5 @@ class res_currency_account(osv.osv): res_currency_account() -# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: \ No newline at end of file +# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: + diff --git a/addons/point_of_sale/__openerp__.py b/addons/point_of_sale/__openerp__.py index 53ddbbab866..24a8db4a3da 100644 --- a/addons/point_of_sale/__openerp__.py +++ b/addons/point_of_sale/__openerp__.py @@ -82,7 +82,13 @@ Main features : 'application': True, 'certificate' : '001156338024966477869', # Web client - 'js': ['static/lib/backbone/backbone-0.5.3.js', 'static/src/js/pos.js'], #, 'static/src/js/pos2.js'], + 'js': [ + 'static/lib/backbone/backbone-0.5.3.js', + 'static/src/js/pos_models.js', + 'static/src/js/pos_widgets.js', + 'static/src/js/pos_devices.js', + 'static/src/js/pos_main.js' + ], 'css': ['static/src/css/pos.css'], 'qweb': ['static/src/xml/pos.xml'], 'auto_install': True, diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index 09b9ed68550..381c30f49de 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -180,7 +180,7 @@ class pos_session(osv.osv): def _create_cash_register(self, cr, uid, pos_config, name, context=None): import pdb - pdb.set_trace() + #pdb.set_trace() if not pos_config: return False @@ -200,7 +200,9 @@ class pos_session(osv.osv): 'name' : name, 'journal_id' : journal_id, } - return proxy.create(cr, uid, values, context=context) + cash_register_id = proxy.create(cr, uid, values, context=context) + + return cash_register_id def create(self, cr, uid, values, context=None): @@ -348,7 +350,7 @@ class pos_order(osv.osv): '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)]}), - #'session_id' : fields.many2one('pos.session', 'Session', required=True, select=1), + 'session_id' : fields.many2one('pos.session', 'Session', required=True, select=1), 'state': fields.selection([('draft', 'New'), ('cancel', 'Cancelled'), diff --git a/addons/point_of_sale/point_of_sale_demo.xml b/addons/point_of_sale/point_of_sale_demo.xml index 8d5fdc67d63..577d025c4fd 100644 --- a/addons/point_of_sale/point_of_sale_demo.xml +++ b/addons/point_of_sale/point_of_sale_demo.xml @@ -670,6 +670,7 @@ 0.83 Belle-Vue Kriek 25cl + 5410228193449 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 @@ -677,12 +678,14 @@ 1.00 Leffe Brune 33cl + 5410228142164 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 1.00 Leffe Blonde 33cl + 5410228142218 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 diff --git a/addons/point_of_sale/point_of_sale_view.xml b/addons/point_of_sale/point_of_sale_view.xml index 7e66333b1d3..43bcc6e69f0 100644 --- a/addons/point_of_sale/point_of_sale_view.xml +++ b/addons/point_of_sale/point_of_sale_view.xml @@ -861,12 +861,22 @@ - + + + + + + + + + + diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index 09fbe3818f2..3769417995b 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -1,4 +1,6 @@ - +body{ + overflow: hidden; +} .point-of-sale { padding: 0; margin: 0; @@ -116,9 +118,15 @@ bottom: 0; } .point-of-sale #leftpane { - height: 100%; + -webkit-box-sizing:border-box; + position:absolute; + left:0; + width:440px; + top:0px; + bottom:105px; + /*height: 87%; width: 440px; - position: relative; + position: relative;*/ border-right: solid 1px #afafb6; background-color: white; } @@ -249,10 +257,10 @@ .point-of-sale #rightpane { position: absolute; top: 0; - bottom: 0; - left: 441px; + bottom: 105px; + left: 440px; + //height:100%; right: 0; - height: 100%; vertical-align: top; } .point-of-sale #rightpane header { @@ -487,3 +495,229 @@ } } +.point-of-sale .pos-actionbar{ + position:absolute; + left: 0; + bottom: 0px; + height: 105px; + width: 100%; + margin: 0; + background: #f5f5f5; /*#ebebeb;*/ + border-top: solid 1px #afafb6; +} + +.point-of-sale .pos-actionbar ul{ + list-style: none; +} + +.point-of-sale .pos-actionbar-left-region{ + height: 100%; + width: 434px; + margin: 0px; + padding-left:3px; + padding-right:3px; + border-right: solid 1px #dfdfdf; + float: left; +} + +.point-of-sale .pos-actionbar-right-region{ + height: 100%; + margin: 0px; + padding-left:3px; + padding-right:3px; + overflow:hidden; +} + +.point-of-sale .pos-actionbar .button{ + width: 90px; + height: 90px; + text-align:center; + margin:3px; + margin-top:6px; + //line-height: 90px; + //vertical-align:center; + float:left; + + font-size: 14px; + font-weight: bold; + + cursor: pointer; + + border: 1px solid #cacaca; + -moz-border-radius: 4px; + -webkit-border-radius: 4px; + border-radius: 4px; + + background: #e2e2e2; + background: -moz-linear-gradient(#f0f0f0, #e2e2e2); + background: -webkit-gradient(linear, left top, left bottom, from(#f0f0f0), to(#e2e2e2)); + -webkit-box-shadow: 0px 2px 2px rgba(0,0,0, 0.3); +} +.point-of-sale .pos-actionbar .button .label{ + margin-top: 37px; +} +.point-of-sale .pos-actionbar .button .icon{ + margin-top: 10px; +} +.point-of-sale .pos-actionbar .button:hover { + color: white; + background: #7f82ac; + border: 1px solid #7f82ac; + background: -moz-linear-gradient(#9d9fc5, #7f82ac); + background: -webkit-gradient(linear, left top, left bottom, from(#9d9fc5), to(#7f82ac)); + + -webkit-transition-property: background, border; + -webkit-transition-duration: 0.2s; + -webkit-transition-timing-function: ease-out; +} + +.point-of-sale .pos-actionbar .button.rightalign{ + float:right; +} + +/* Onscreen Keyboard http://net.tutsplus.com/tutorials/javascript-ajax/creating-a-keyboard-with-css-and-jquery/ */ + +.point-of-sale .keyboard_frame{ + display: none; + position:absolute; + left: 0; + bottom: 0px; + margin: 0; + padding: 0; + padding-top: 15px; + width: 100%; + height: 0px; /* 235px, animated via jquery */ + background-color: #BBB; + overflow:hidden; + -webkit-box-shadow: 0px 0px 10px rgba(0,0,0, 0.3); + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + +} +.point-of-sale .keyboard_frame .close_button{ + height:40px; + width:60px; + text-align:center; + background-color: #DDD; + font-size: 12px; + line-height:40px; + border: 1px solid #CCC; + -webkit-border-radius: 5px; + -webkit-box-shadow: 0px 2px 5px rgba(0,0,0, 0.2); + position:absolute; + top:0; + right:15px; + cursor: pointer; +} +.point-of-sale .keyboard li { + float: left; + text-align: center; + background-color: #fff; + border: 1px solid #f0f0f0; + top:0; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + -webkit-box-shadow: 0px 2px 5px rgba(0,0,0, 0.2); + -webkit-transition-property: top, background-color; + -webkit-transition-duration: 0.2s; + -webkit-transition-timing-function: linear; +} +.point-of-sale .keyboard li:hover { + position: relative; + top: 2px; + left: 0px; + border-color: #ddd; + background-color:#e5e5e5; + cursor: pointer; + -webkit-transition-property: top, background-color; + -webkit-transition-duration: 0.1s; + -webkit-transition-timing-function: ease-out; +} +.point-of-sale .uppercase { + text-transform: uppercase; +} +.point-of-sale .on { + display: none; +} +.point-of-sale .firstitem{ + clear: left; +} +.point-of-sale .keyboard .lastitem { + margin-right: 0; +} + +/* ---- full sized keyboard ---- */ + +.point-of-sale .full_keyboard { + list-style: none; + font-size: 14px; + width: 680px; + height: 100%; + margin-left: auto; + margin-right: auto; +} +.point-of-sale .full_keyboard li{ + margin: 0 5px 5px 0; + width: 40px; + height: 40px; + line-height: 40px; +} +.point-of-sale .full_keyboard .tab, .point-of-sale .full_keyboard .delete { + width: 70px; +} +.point-of-sale .full_keyboard .capslock { + width: 80px; +} +.point-of-sale .full_keyboard .return { + width: 77px; +} +.point-of-sale .full_keyboard .left-shift { + width: 95px; +} +.point-of-sale .full_keyboard .right-shift { + width: 109px; +} +.point-of-sale .full_keyboard .space { + clear: left; + width: 673px; +} + +/* ---- simplified keyboard ---- */ + +.point-of-sale .simple_keyboard { + list-style: none; + font-size: 16px; + width: 545px; + height: 220px; + margin-left: auto; + margin-right: auto; +} +.point-of-sale .simple_keyboard li{ + margin: 0 5px 5px 0; + width: 49px; + height: 49px; + line-height: 49px; +} +.point-of-sale .simple_keyboard .firstitem.row_asdf{ + margin-left:25px; +} +.point-of-sale .simple_keyboard .firstitem.row_zxcv{ + margin-left:55px; +} +.point-of-sale .simple_keyboard .delete{ + width: 103px; +} +.point-of-sale .simple_keyboard .return{ + width: 103px; +} +.point-of-sale .simple_keyboard .space{ + width:268px; +} +.point-of-sale .simple_keyboard .numlock{ + width:103px; +} + diff --git a/addons/point_of_sale/static/src/img/icons/png48/face-monkey.png b/addons/point_of_sale/static/src/img/icons/png48/face-monkey.png new file mode 100644 index 00000000000..1a57c9e0696 Binary files /dev/null and b/addons/point_of_sale/static/src/img/icons/png48/face-monkey.png differ diff --git a/addons/point_of_sale/static/src/img/icons/png48/go-next.png b/addons/point_of_sale/static/src/img/icons/png48/go-next.png new file mode 100644 index 00000000000..bcd343dde94 Binary files /dev/null and b/addons/point_of_sale/static/src/img/icons/png48/go-next.png differ diff --git a/addons/point_of_sale/static/src/img/icons/png48/help-browser.png b/addons/point_of_sale/static/src/img/icons/png48/help-browser.png new file mode 100644 index 00000000000..8c56bd4f962 Binary files /dev/null and b/addons/point_of_sale/static/src/img/icons/png48/help-browser.png differ diff --git a/addons/point_of_sale/static/src/js/pos.js b/addons/point_of_sale/static/src/js/pos.js.THIS similarity index 100% rename from addons/point_of_sale/static/src/js/pos.js rename to addons/point_of_sale/static/src/js/pos.js.THIS diff --git a/addons/point_of_sale/static/src/js/pos_devices.js b/addons/point_of_sale/static/src/js/pos_devices.js new file mode 100644 index 00000000000..31ac45f856c --- /dev/null +++ b/addons/point_of_sale/static/src/js/pos_devices.js @@ -0,0 +1,149 @@ + +function openerp_pos_devices(module, instance){ //module is instance.point_of_sale + module.BarcodeReader = instance.web.Class.extend({ + + init: function(attributes){ + this.pos = attributes.pos; + this.connect(); + }, + + // returns true if the code is a valid EAN codebar number by checking the control digit. + checkEan: function(code){ + var st1 = code.slice(); + var st2 = st1.slice(0,st1.length-1).reverse(); + // some EAN13 barcodes have a length of 12, as they start by 0 + while (st2.length < 12) { + st2.push(0); + } + var countSt3 = 1; + var st3 = 0; + $.each(st2, function() { + if (countSt3%2 === 1) { + st3 += this; + } + countSt3 ++; + }); + st3 *= 3; + var st4 = 0; + var countSt4 = 1; + $.each(st2, function() { + if (countSt4%2 === 0) { + st4 += this; + } + countSt4 ++; + }); + var st5 = st3 + st4; + var cd = (10 - (st5%10)) % 10; + return code[code.length-1] === cd; + }, + + // returns a product that has a packaging with an EAN matching to provided ean string. + // returns undefined if no such product is found. + getProductByEAN: function(ean) { + var allProducts = this.pos.get('product_list'); + var allPackages = this.pos.get('product.packaging'); + var prefix = ean.substring(0,2); + var scannedProductModel = undefined; + + if (prefix in {'02':'', '22':'', '24':'', '26':'', '28':''}) { + + // PRICE barcode + var itemCode = ean.substring(0,7); + var scannedPackaging = _.detect(allPackages, function(pack) { return pack.ean !== undefined && pack.ean.substring(0,7) === itemCode;}); + if (scannedPackaging !== undefined) { + scannedProductModel = _.detect(allProducts, function(pc) { return pc.id === scannedPackaging.product_id[0];}); + scannedProductModel.list_price = Number(ean.substring(7,12))/100; + } + } else if (prefix in {'21':'','23':'','27':'','29':'','25':''}) { + // WEIGHT barcode + var weight = Number(barcode.substring(7,12))/1000; + var itemCode = ean.substring(0,7); + var scannedPackaging = _.detect(allPackages, function(pack) { return pack.ean !== undefined && pack.ean.substring(0,7) === itemCode;}); + if (scannedPackaging !== undefined) { + scannedProductModel = _.detect(allProducts, function(pc) { return pc.id === scannedPackaging.product_id[0];}); + scannedProductModel.list_price *= weight; + scannedProductModel.name += ' - ' + weight + ' Kg.'; + } + } else { + // UNIT barcode + scannedProductModel = _.detect(allProducts, function(pc) { return pc.ean13 === ean;}); //TODO DOES NOT SCALE + } + return scannedProductModel; + }, + + // starts catching keyboard events and tries to interpret codebar + connect: function(){ + var self = this; + var codeNumbers = []; + var timeStamp = 0; + var lastTimeStamp = 0; + + // The barcode readers acts as a keyboard, we catch all keyup events and try to find a + // barcode sequence in the typed keys, then act accordingly. + $('body').delegate('','keyup', function (e){ + + //We only care about numbers + if (!isNaN(Number(String.fromCharCode(e.keyCode)))) { + + // The barcode reader sends keystrokes with a specific interval. + // We look if the typed keys fit in the interval. + if (codeNumbers.length==0) { + timeStamp = new Date().getTime(); + } else { + if (lastTimeStamp + 30 < new Date().getTime()) { + // not a barcode reader + codeNumbers = []; + timeStamp = new Date().getTime(); + } + } + codeNumbers.push(e.keyCode - 48); + lastTimeStamp = new Date().getTime(); + if (codeNumbers.length == 13) { + //console.log('found code:', codeNumbers.join('')); + + // a barcode reader + if (!self.checkEan(codeNumbers)) { + // barcode read error, raise warning + $(QWeb.render('pos-scan-warning')).dialog({ + resizable: false, + height:220, + modal: true, + title: "Warning", + }); + } + var selectedOrder = self.pos.get('selectedOrder'); + var scannedProductModel = self.getProductByEAN(codeNumbers.join(''),allPackages,allProducts); + if (scannedProductModel === undefined) { + // product not recognized, raise warning + $(QWeb.render('pos-scan-warning')).dialog({ + resizable: false, + height:220, + modal: true, + title: "Warning", + /* + buttons: { + "OK": function() { + $( this ).dialog( "close" ); + return; + }, + }*/ + }); + } else { + selectedOrder.addProduct(new module.Product(scannedProductModel)); + } + + codeNumbers = []; + } + } else { + // NaN + codeNumbers = []; + } + }); + }, + + // stops catching keyboard events + disconnect: function(){ + $('body').undelegate('', 'keyup') + }, + }); +} diff --git a/addons/point_of_sale/static/src/js/pos_main.js b/addons/point_of_sale/static/src/js/pos_main.js new file mode 100644 index 00000000000..09e51dd41e1 --- /dev/null +++ b/addons/point_of_sale/static/src/js/pos_main.js @@ -0,0 +1,17 @@ + +openerp.point_of_sale = function(instance) { + + instance.point_of_sale = {}; + + var module = instance.point_of_sale; + + openerp_pos_models(module,instance); // import pos_models.js + + openerp_pos_widgets(module,instance); // import pos_widgets.js + + openerp_pos_devices(module,instance); // import pos_devices.js + + instance.web.client_actions.add('pos.ui', 'instance.point_of_sale.PosWidget'); +}; + + diff --git a/addons/point_of_sale/static/src/js/pos_models.js b/addons/point_of_sale/static/src/js/pos_models.js new file mode 100644 index 00000000000..9d802b44597 --- /dev/null +++ b/addons/point_of_sale/static/src/js/pos_models.js @@ -0,0 +1,550 @@ +function openerp_pos_models(module, instance){ //module is instance.point_of_sale + var QWeb = instance.web.qweb; + + module.LocalStorageDAO = instance.web.Class.extend({ + add_operation: function(operation) { + var self = this; + return $.async_when().pipe(function() { + var tmp = self._get('oe_pos_operations', []); + var last_id = self._get('oe_pos_operations_sequence', 1); + tmp.push({'id': last_id, 'data': operation}); + self._set('oe_pos_operations', tmp); + self._set('oe_pos_operations_sequence', last_id + 1); + }); + }, + remove_operation: function(id) { + var self = this; + return $.async_when().pipe(function() { + var tmp = self._get('oe_pos_operations', []); + tmp = _.filter(tmp, function(el) { + return el.id !== id; + }); + self._set('oe_pos_operations', tmp); + }); + }, + get_operations: function() { + var self = this; + return $.async_when().pipe(function() { + return self._get('oe_pos_operations', []); + }); + }, + _get: function(key, default_) { + var txt = localStorage[key]; + if (! txt) + return default_; + return JSON.parse(txt); + }, + _set: function(key, value) { + localStorage[key] = JSON.stringify(value); + }, + }); + + var fetch = function(osvModel, fields, domain){ + var dataSetSearch = new instance.web.DataSetSearch(null, osvModel, {}, domain); + return dataSetSearch.read_slice(fields, 0); + }; + + /* + Gets all the necessary data from the OpenERP web client (instance, shop data etc.) + */ + module.PosModel = Backbone.Model.extend({ + initialize: function(session, attributes) { + Backbone.Model.prototype.initialize.call(this, attributes); + var self = this; + this.dao = new module.LocalStorageDAO(); + this.ready = $.Deferred(); + this.flush_mutex = new $.Mutex(); + this.build_tree = _.bind(this.build_tree, this); + this.session = session; + this.categories = {}; + this.set({ + 'nbr_pending_operations': 0, + 'currency': {symbol: '$', position: 'after'}, + 'shop': {}, + 'company': {}, + 'user': {}, + 'orders': new module.OrderCollection(), + 'products': new module.ProductCollection(), + //'cashRegisters': [], // new module.CashRegisterCollection(this.pos.get('bank_statements')), + 'selectedOrder': undefined, + }); + + var cat_def = fetch('pos.category', ['name', 'parent_id', 'child_id']) + .pipe(function(result){ + return self.set({'categories': result}); + }); + + var prod_def = fetch( + 'product.product', + ['name', 'list_price', 'pos_categ_id', 'taxes_id','product_image_small'], + [['pos_categ_id','!=', 'false']] + ).then(function(result){ + return self.set({'product_list': result}); + }); + + var bank_def = fetch( + 'account.bank.statement', + ['account_id', 'currency', 'journal_id', 'state', 'name'], + [['state','=','open'], ['user_id', '=', this.session.uid]] + ).then(function(result){ + return self.set({'bank_statements': result}); + }); + + var tax_def = fetch('account.tax', ['amount','price_include','type']) + .then(function(result){ + return self.set({'taxes': result}); + }); + + $.when(cat_def,prod_def,bank_def,tax_def,this.get_app_data(), this.flush()) + .pipe(_.bind(this.build_tree, this)) + .pipe(function(){ + self.set({'cashRegisters': new module.CashRegisterCollection(self.get('bank_statements')) }); + self.ready.resolve(); + }); + + return (this.get('orders')).bind('remove', _.bind( function(removedOrder) { + if ((this.get('orders')).isEmpty()) { + this.addAndSelectOrder(new module.Order({pos: self})); + } + if ((this.get('selectedOrder')) === removedOrder) { + return this.set({ + selectedOrder: (this.get('orders')).last() + }); + } + }, this)); + + }, + + get_app_data: function() { + var self = this; + return $.when(new instance.web.Model("sale.shop").get_func("search_read")([]).pipe(function(result) { + self.set({'shop': result[0]}); + var company_id = result[0]['company_id'][0]; + return new instance.web.Model("res.company").get_func("read")(company_id, ['currency_id', 'name', 'phone']).pipe(function(result) { + self.set({'company': result}); + var currency_id = result['currency_id'][0] + return new instance.web.Model("res.currency").get_func("read")([currency_id], + ['symbol', 'position']).pipe(function(result) { + self.set({'currency': result[0]}); + + }); + }); + }), new instance.web.Model("res.users").get_func("read")(this.session.uid, ['name']).pipe(function(result) { + self.set({'user': result}); + })); + }, + push_order: function(record) { + var self = this; + return this.dao.add_operation(record).pipe(function(){ + return self.flush(); + }); + }, + addAndSelectOrder: function(newOrder) { + (this.get('orders')).add(newOrder); + return this.set({ + selectedOrder: newOrder + }); + }, + flush: function() { + return this.flush_mutex.exec(_.bind(function() { + return this._int_flush(); + }, this)); + }, + _int_flush : function() { + var self = this; + + this.dao.get_operations().pipe(function(operations) { + self.set( {'nbr_pending_operations':operations.length} ); + if(operations.length === 0){ + return $.when(); + } + var op = operations[0]; + + // we prevent the default error handler and assume errors + // are a normal use case, except we stop the current iteration + + return new instance.web.Model('pos.order').get_func('create_from_ui')([op]) + .fail(function(unused, event){ + event.preventDefault(); + }) + .pipe(function(){ + console.debug('saved 1 record'); //TODO Debug this + self.dao.remove_operation(operations[0].id).pipe(function(){ + return self._int_flush(); + }); + }, function(){ + return $.when(); + }); + }); + }, + build_tree: function() { + var c, id, _i, _len, _ref, _ref2; + _ref = this.get('categories'); + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + c = _ref[_i]; + this.categories[c.id] = { + id: c.id, + name: c.name, + children: c.child_id, + parent: c.parent_id[0], + ancestors: [c.id], + subtree: [c.id] + }; + } + _ref2 = this.categories; + for (id in _ref2) { + c = _ref2[id]; + this.current_category = c; + this.build_ancestors(c.parent); + this.build_subtree(c); + } + this.categories[0] = { + ancestors: [], + children: (function() { + var _j, _len2, _ref3, _results; + _ref3 = this.get('categories'); + _results = []; + for (_j = 0, _len2 = _ref3.length; _j < _len2; _j++) { + c = _ref3[_j]; + if (!(c.parent_id[0] != null)) { + _results.push(c.id); + } + } + return _results; + }).call(this), + subtree: (function() { + var _j, _len2, _ref3, _results; + _ref3 = this.get('categories'); + _results = []; + for (_j = 0, _len2 = _ref3.length; _j < _len2; _j++) { + c = _ref3[_j]; + _results.push(c.id); + } + return _results; + }).call(this) + }; + }, + build_ancestors: function(parent) { + if (parent != null) { + this.current_category.ancestors.unshift(parent); + return this.build_ancestors(this.categories[parent].parent); + } + }, + build_subtree: function(category) { + var c, _i, _len, _ref, _results; + _ref = category.children; + _results = []; + for (_i = 0, _len = _ref.length; _i < _len; _i++) { + c = _ref[_i]; + this.current_category.subtree.push(c); + _results.push(this.build_subtree(this.categories[c])); + } + return _results; + } + }); + + module.CashRegister = Backbone.Model.extend({ + }); + + module.CashRegisterCollection = Backbone.Collection.extend({ + model: module.CashRegister, + }); + + module.Product = Backbone.Model.extend({ + }); + + module.ProductCollection = Backbone.Collection.extend({ + model: module.Product, + }); + + /* + Each Order contains zero or more Orderlines (i.e. the content of the "shopping cart".) + There should only ever be one Orderline per distinct product in an Order. + To add more of the same product, just update the quantity accordingly. + The Order also contains payment information. + */ + module.Orderline = Backbone.Model.extend({ + defaults: { + quantity: 1, + list_price: 0, + discount: 0 + }, + initialize: function(attributes) { + this.pos = attributes.pos; + Backbone.Model.prototype.initialize.apply(this, arguments); + this.bind('change:quantity', function(unused, qty) { + if (qty == 0) + this.trigger('killme'); + }, this); + }, + incrementQuantity: function() { + return this.set({ + quantity: (this.get('quantity')) + 1 + }); + }, + getPriceWithoutTax: function() { + return this.getAllPrices().priceWithoutTax; + }, + getPriceWithTax: function() { + return this.getAllPrices().priceWithTax; + }, + getTax: function() { + return this.getAllPrices().tax; + }, + getAllPrices: function() { + var self = this; + var base = (this.get('quantity')) * (this.get('list_price')) * (1 - (this.get('discount')) / 100); + var totalTax = base; + var totalNoTax = base; + + var product_list = self.pos.get('product_list'); + var product = _.detect(product_list, function(el) {return el.id === self.get('id');}); + var taxes_ids = product.taxes_id; + var taxes = self.pos.get('taxes'); + var taxtotal = 0; + _.each(taxes_ids, function(el) { + var tax = _.detect(taxes, function(t) {return t.id === el;}); + if (tax.price_include) { + var tmp; + if (tax.type === "percent") { + tmp = base - (base / (1 + tax.amount)); + } else if (tax.type === "fixed") { + tmp = tax.amount * self.get('quantity'); + } else { + throw "This type of tax is not supported by the point of sale: " + tax.type; + } + taxtotal += tmp; + totalNoTax -= tmp; + } else { + var tmp; + if (tax.type === "percent") { + tmp = tax.amount * base; + } else if (tax.type === "fixed") { + tmp = tax.amount * self.get('quantity'); + } else { + throw "This type of tax is not supported by the point of sale: " + tax.type; + } + taxtotal += tmp; + totalTax += tmp; + } + }); + return { + "priceWithTax": totalTax, + "priceWithoutTax": totalNoTax, + "tax": taxtotal, + }; + }, + exportAsJSON: function() { + return { + qty: this.get('quantity'), + price_unit: this.get('list_price'), + discount: this.get('discount'), + product_id: this.get('id') + }; + }, + }); + + module.OrderlineCollection = Backbone.Collection.extend({ + model: module.Orderline, + }); + + // Every PaymentLine has all the attributes of the corresponding CashRegister. + module.Paymentline = Backbone.Model.extend({ + defaults: { + amount: 0, + }, + initialize: function(attributes) { + Backbone.Model.prototype.initialize.apply(this, arguments); + }, + getAmount: function(){ + return this.get('amount'); + }, + exportAsJSON: function(){ + return { + name: instance.web.datetime_to_str(new Date()), + statement_id: this.get('id'), + account_id: (this.get('account_id'))[0], + journal_id: (this.get('journal_id'))[0], + amount: this.getAmount() + }; + }, + }); + + module.PaymentlineCollection = Backbone.Collection.extend({ + model: module.Paymentline, + }); + + module.Order = Backbone.Model.extend({ + defaults:{ + validated: false, + step: 'products', + }, + initialize: function(attributes){ + Backbone.Model.prototype.initialize.apply(this, arguments); + this.set({ + creationDate: new Date(), + orderLines: new module.OrderlineCollection(), + paymentLines: new module.PaymentlineCollection(), + name: "Order " + this.generateUniqueId(), + }); + this.pos = attributes.pos; //TODO put that in set and remember to use 'get' to read it ... + this.bind('change:validated', this.validatedChanged); + return this; + }, + events: { + 'change:validated': 'validatedChanged' + }, + validatedChanged: function() { + if (this.get("validated") && !this.previous("validated")) { + this.set({'step': 'receipt'}); + } + }, + generateUniqueId: function() { + return new Date().getTime(); + }, + addProduct: function(product) { + var existing; + existing = (this.get('orderLines')).get(product.id); + if (existing != null) { + existing.incrementQuantity(); + } else { + var attr = product.toJSON(); + attr.pos = this.pos; + var line = new module.Orderline(attr); + this.get('orderLines').add(line); + line.bind('killme', function() { + this.get('orderLines').remove(line); + }, this); + } + }, + addPaymentLine: function(cashRegister) { + var newPaymentline; + newPaymentline = new module.Paymentline(cashRegister); + /* TODO: Should be 0 for cash-like accounts */ + newPaymentline.set({ + amount: this.getDueLeft() + }); + return (this.get('paymentLines')).add(newPaymentline); + }, + getName: function() { + return this.get('name'); + }, + getTotal: function() { + return (this.get('orderLines')).reduce((function(sum, orderLine) { + return sum + orderLine.getPriceWithTax(); + }), 0); + }, + getTotalTaxExcluded: function() { + return (this.get('orderLines')).reduce((function(sum, orderLine) { + return sum + orderLine.getPriceWithoutTax(); + }), 0); + }, + getTax: function() { + return (this.get('orderLines')).reduce((function(sum, orderLine) { + return sum + orderLine.getTax(); + }), 0); + }, + getPaidTotal: function() { + return (this.get('paymentLines')).reduce((function(sum, paymentLine) { + return sum + paymentLine.getAmount(); + }), 0); + }, + getChange: function() { + return this.getPaidTotal() - this.getTotal(); + }, + getDueLeft: function() { + return this.getTotal() - this.getPaidTotal(); + }, + exportAsJSON: function() { + var orderLines, paymentLines; + orderLines = []; + (this.get('orderLines')).each(_.bind( function(item) { + return orderLines.push([0, 0, item.exportAsJSON()]); + }, this)); + paymentLines = []; + (this.get('paymentLines')).each(_.bind( function(item) { + return paymentLines.push([0, 0, item.exportAsJSON()]); + }, this)); + return { + name: this.getName(), + amount_paid: this.getPaidTotal(), + amount_total: this.getTotal(), + amount_tax: this.getTax(), + amount_return: this.getChange(), + lines: orderLines, + statement_ids: paymentLines + }; + }, + }); + + module.OrderCollection = Backbone.Collection.extend({ + model: module.Order, + }); + + /* + The numpad handles both the choice of the property currently being modified + (quantity, price or discount) and the edition of the corresponding numeric value. + */ + module.NumpadState = Backbone.Model.extend({ + defaults: { + buffer: "0", + mode: "quantity" + }, + appendNewChar: function(newChar) { + var oldBuffer; + oldBuffer = this.get('buffer'); + if (oldBuffer === '0') { + this.set({ + buffer: newChar + }); + } else if (oldBuffer === '-0') { + this.set({ + buffer: "-" + newChar + }); + } else { + this.set({ + buffer: (this.get('buffer')) + newChar + }); + } + this.updateTarget(); + }, + deleteLastChar: function() { + var tempNewBuffer; + tempNewBuffer = (this.get('buffer')).slice(0, -1) || "0"; + if (isNaN(tempNewBuffer)) { + tempNewBuffer = "0"; + } + this.set({ + buffer: tempNewBuffer + }); + this.updateTarget(); + }, + switchSign: function() { + var oldBuffer; + oldBuffer = this.get('buffer'); + this.set({ + buffer: oldBuffer[0] === '-' ? oldBuffer.substr(1) : "-" + oldBuffer + }); + this.updateTarget(); + }, + changeMode: function(newMode) { + this.set({ + buffer: "0", + mode: newMode + }); + }, + reset: function() { + this.set({ + buffer: "0", + mode: "quantity" + }); + }, + updateTarget: function() { + var bufferContent, params; + bufferContent = this.get('buffer'); + if (bufferContent && !isNaN(bufferContent)) { + this.trigger('setValue', parseFloat(bufferContent)); + } + }, + }); +} diff --git a/addons/point_of_sale/static/src/js/pos_widgets.js b/addons/point_of_sale/static/src/js/pos_widgets.js new file mode 100644 index 00000000000..9e03ac4dcd7 --- /dev/null +++ b/addons/point_of_sale/static/src/js/pos_widgets.js @@ -0,0 +1,1106 @@ +function openerp_pos_widgets(module, instance){ //module is instance.point_of_sale + var QWeb = instance.web.qweb; + + var qweb_template = function(template,pos){ + return function(ctx){ + if(!pos){ //this is a huge hack that needs to be removed ... TODO + var HackPosModel = Backbone.Model.extend({ + initialize:function(){ + this.set({ + 'currency': {symbol: '$', position: 'after'}, + }); + }, + }); + pos = new HackPosModel(); + } + return QWeb.render(template, _.extend({}, ctx,{ + 'currency': pos.get('currency'), + 'format_amount': function(amount) { + if (pos.get('currency').position == 'after') { + return amount + ' ' + pos.get('currency').symbol; + } else { + return pos.get('currency').symbol + ' ' + amount; + } + }, + })); + }; + }; + + module.NumpadWidget = instance.web.OldWidget.extend({ + init: function(parent, options) { + this._super(parent); + this.state = new module.NumpadState(); + }, + start: function() { + this.state.bind('change:mode', this.changedMode, this); + this.changedMode(); + this.$element.find('button#numpad-backspace').click(_.bind(this.clickDeleteLastChar, this)); + this.$element.find('button#numpad-minus').click(_.bind(this.clickSwitchSign, this)); + this.$element.find('button.number-char').click(_.bind(this.clickAppendNewChar, this)); + this.$element.find('button.mode-button').click(_.bind(this.clickChangeMode, this)); + }, + clickDeleteLastChar: function() { + return this.state.deleteLastChar(); + }, + clickSwitchSign: function() { + return this.state.switchSign(); + }, + clickAppendNewChar: function(event) { + var newChar; + newChar = event.currentTarget.innerText || event.currentTarget.textContent; + return this.state.appendNewChar(newChar); + }, + clickChangeMode: function(event) { + var newMode = event.currentTarget.attributes['data-mode'].nodeValue; + return this.state.changeMode(newMode); + }, + changedMode: function() { + var mode = this.state.get('mode'); + $('.selected-mode').removeClass('selected-mode'); + $(_.str.sprintf('.mode-button[data-mode="%s"]', mode), this.$element).addClass('selected-mode'); + }, + }); + /* + Gives access to the payment methods (aka. 'cash registers') + */ + module.PaypadWidget = instance.web.OldWidget.extend({ + init: function(parent, options) { + this._super(parent); + this.pos = options.pos; + }, + start: function() { + this.$element.find('button').click(_.bind(this.performPayment, this)); + }, + performPayment: function(event) { + if (this.pos.get('selectedOrder').get('step') === 'receipt') + return; + var cashRegister, cashRegisterCollection, cashRegisterId; + /* set correct view */ + this.pos.get('selectedOrder').set({'step': 'payment'}); + + cashRegisterId = event.currentTarget.attributes['cash-register-id'].nodeValue; + cashRegisterCollection = this.pos.get('cashRegisters'); + cashRegister = cashRegisterCollection.find(_.bind( function(item) { + return (item.get('id')) === parseInt(cashRegisterId, 10); + }, this)); + return (this.pos.get('selectedOrder')).addPaymentLine(cashRegister); + }, + renderElement: function() { + this.$element.empty(); + return (this.pos.get('cashRegisters')).each(_.bind( function(cashRegister) { + var button = new module.PaymentButtonWidget(); + button.model = cashRegister; + button.appendTo(this.$element); + }, this)); + } + }); + + module.PaymentButtonWidget = instance.web.OldWidget.extend({ + template_fct: qweb_template('pos-payment-button-template'), + renderElement: function() { + this.$element.html(this.template_fct({ + id: this.model.get('id'), + name: (this.model.get('journal_id'))[1] + })); + return this; + } + }); + /* + There are 3 steps in a POS workflow: + 1. prepare the order (i.e. chose products, quantities etc.) + 2. choose payment method(s) and amount(s) + 3. validae order and print receipt + It should be possible to go back to any step as long as step 3 hasn't been completed. + Modifying an order after validation shouldn't be allowed. + */ + module.StepSwitcher = instance.web.OldWidget.extend({ + init: function(parent, options) { + this._super(parent); + this.pos = options.pos; + this.change_order(); + this.pos.bind('change:selectedOrder', this.change_order, this); + }, + change_order: function() { + if (this.selected_order) { + this.selected_order.unbind('change:step', this.change_step); + } + this.selected_order = this.pos.get('selectedOrder'); + if (this.selected_order) { + this.selected_order.bind('change:step', this.change_step, this); + } + this.change_step(); + }, + change_step: function() { + var new_step = this.selected_order ? this.selected_order.get('step') : 'products'; + $('.step-screen').hide(); + $('#' + new_step + '-screen').show(); + }, + }); + +// ---------- "Shopping Carts" ---------- + + module.OrderlineWidget = instance.web.OldWidget.extend({ + tagName: 'tr', + template_fct: qweb_template('pos-orderline-template'), + init: function(parent, options) { + this._super(parent); + this.model = options.model; + this.model.bind('change', _.bind( function() { + this.refresh(); + }, this)); + this.model.bind('remove', _.bind( function() { + this.$element.remove(); + }, this)); + this.order = options.order; + }, + start: function() { + this.$element.click(_.bind(this.clickHandler, this)); + this.refresh(); + }, + clickHandler: function() { + this.select(); + }, + renderElement: function() { + this.$element.html(this.template_fct(this.model.toJSON())); + this.select(); + }, + refresh: function() { + this.renderElement(); + var heights = _.map(this.$element.prevAll(), function(el) {return $(el).outerHeight();}); + heights.push($('#current-order thead').outerHeight()); + var position = _.reduce(heights, function(memo, num){ return memo + num; }, 0); + $('#current-order').scrollTop(position); + }, + select: function() { + $('tr.selected').removeClass('selected'); + this.$element.addClass('selected'); + this.order.selected = this.model; + this.on_selected(); + }, + on_selected: function() {}, + }); + + module.OrderWidget = instance.web.OldWidget.extend({ + init: function(parent, options) { + this._super(parent); + this.pos = options.pos; + this.setNumpadState(options.numpadState); + this.pos.bind('change:selectedOrder', this.changeSelectedOrder, this); + this.bindOrderLineEvents(); + }, + setNumpadState: function(numpadState) { + if (this.numpadState) { + this.numpadState.unbind('setValue', this.setValue); + } + this.numpadState = numpadState; + if (this.numpadState) { + this.numpadState.bind('setValue', this.setValue, this); + this.numpadState.reset(); + } + }, + setValue: function(val) { + var param = {}; + param[this.numpadState.get('mode')] = val; + var order = this.pos.get('selectedOrder'); + if (order.get('orderLines').length !== 0) { + order.selected.set(param); + } else { + this.pos.get('selectedOrder').destroy(); + } + }, + changeSelectedOrder: function() { + this.currentOrderLines.unbind(); + this.bindOrderLineEvents(); + this.renderElement(); + }, + bindOrderLineEvents: function() { + this.currentOrderLines = (this.pos.get('selectedOrder')).get('orderLines'); + this.currentOrderLines.bind('add', this.addLine, this); + this.currentOrderLines.bind('remove', this.renderElement, this); + }, + addLine: function(newLine) { + var line = new module.OrderlineWidget(null, { + model: newLine, + order: this.pos.get('selectedOrder') + }); + line.on_selected.add(_.bind(this.selectedLine, this)); + this.selectedLine(); + line.appendTo(this.$element); + this.updateSummary(); + }, + selectedLine: function() { + var reset = false; + if (this.currentSelected !== this.pos.get('selectedOrder').selected) { + reset = true; + } + this.currentSelected = this.pos.get('selectedOrder').selected; + if (reset && this.numpadState) + this.numpadState.reset(); + this.updateSummary(); + }, + renderElement: function() { + this.$element.empty(); + this.currentOrderLines.each(_.bind( function(orderLine) { + var line = new module.OrderlineWidget(null, { + model: orderLine, + order: this.pos.get('selectedOrder') + }); + line.on_selected.add(_.bind(this.selectedLine, this)); + line.appendTo(this.$element); + }, this)); + this.updateSummary(); + }, + updateSummary: function() { + var currentOrder, tax, total, totalTaxExcluded; + currentOrder = this.pos.get('selectedOrder'); + total = currentOrder.getTotal(); + totalTaxExcluded = currentOrder.getTotalTaxExcluded(); + tax = currentOrder.getTax(); + $('#subtotal').html(totalTaxExcluded.toFixed(2)).hide().fadeIn(); + $('#tax').html(tax.toFixed(2)).hide().fadeIn(); + $('#total').html(total.toFixed(2)).hide().fadeIn(); + }, + }); + +// ---------- "Products" step. ---------- + + module.CategoryWidget = instance.web.OldWidget.extend({ + init: function(parent, options){ + this._super(parent,options.element_id); + this.pos = options.pos; + }, + start: function() { + this.$element.find(".oe-pos-categories-list a").click(_.bind(this.changeCategory, this)); + }, + template_fct: qweb_template('pos-category-template'), + renderElement: function() { + var self = this; + var c; + this.$element.html(this.template_fct({ + breadcrumb: (function() { + var _i, _len, _results; + _results = []; + for (_i = 0, _len = self.ancestors.length; _i < _len; _i++) { + c = self.ancestors[_i]; + _results.push(self.pos.categories[c]); + } + return _results; + })(), + categories: (function() { + var _i, _len, _results; + _results = []; + for (_i = 0, _len = self.children.length; _i < _len; _i++) { + c = self.children[_i]; + _results.push(self.pos.categories[c]); + } + return _results; + })() + })); + }, + changeCategory: function(a) { + var id = $(a.target).data("category-id"); + this.on_change_category(id); + }, + on_change_category: function(id) {}, + }); + + module.ProductWidget = instance.web.OldWidget.extend({ + tagName:'li', + template_fct: qweb_template('pos-product-template'), + init: function(parent, options) { + this._super(parent); + this.model = options.model; + this.pos = options.pos; + }, + start: function(options) { + $("a", this.$element).click(_.bind(this.addToOrder, this)); + }, + addToOrder: function(event) { + /* Preserve the category URL */ + event.preventDefault(); + return (this.pos.get('selectedOrder')).addProduct(this.model); + }, + renderElement: function() { + this.$element.addClass("product"); + this.$element.html(this.template_fct(this.model.toJSON())); + return this; + }, + }); + + module.ProductListWidget = instance.web.OldWidget.extend({ + init: function(parent, options) { + this._super(parent); + this.model = options.model; + this.pos = options.pos; + this.pos.get('products').bind('reset', this.renderElement, this); + }, + renderElement: function() { + this.$element.empty(); + (this.pos.get('products')).each(_.bind( function(product) { + var p = new module.ProductWidget(null, { + model: product, + pos: this.pos + }); + p.appendTo(this.$element); + }, this)); + return this; + }, + }); + +// ---------- "Payment" step. ---------- + + module.PaymentlineWidget = instance.web.OldWidget.extend({ + tagName: 'tr', + template_fct: qweb_template('pos-paymentline-template'), + init: function(parent, options) { + this._super(parent); + this.model = options.model; + this.model.bind('change', this.changedAmount, this); + }, + on_delete: function() {}, + changeAmount: function(event) { + var newAmount; + newAmount = event.currentTarget.value; + if (newAmount && !isNaN(newAmount)) { + this.amount = parseFloat(newAmount); + this.model.set({ + amount: this.amount, + }); + } + }, + changedAmount: function() { + if (this.amount !== this.model.get('amount')) + this.renderElement(); + }, + renderElement: function() { + this.amount = this.model.get('amount'); + this.$element.html(this.template_fct({ + name: (this.model.get('journal_id'))[1], + amount: this.amount, + })); + this.$element.addClass('paymentline'); + $('input', this.$element).keyup(_.bind(this.changeAmount, this)); + $('.delete-payment-line', this.$element).click(this.on_delete); + }, + }); + + module.PaymentWidget = instance.web.OldWidget.extend({ + init: function(parent, options) { + this._super(parent); + this.model = options.model; + this.pos = options.pos; + this.pos.bind('change:selectedOrder', this.changeSelectedOrder, this); + this.bindPaymentLineEvents(); + this.bindOrderLineEvents(); + }, + paymentLineList: function() { + return this.$element.find('#paymentlines'); + }, + start: function() { + $('button#validate-order', this.$element).click(_.bind(this.validateCurrentOrder, this)); + $('.oe-back-to-products', this.$element).click(_.bind(this.back, this)); + }, + back: function() { + this.pos.get('selectedOrder').set({"step": "products"}); + }, + validateCurrentOrder: function() { + var callback, currentOrder; + currentOrder = this.pos.get('selectedOrder'); + $('button#validate-order', this.$element).attr('disabled', 'disabled'); + this.pos.push_order(currentOrder.exportAsJSON()).then(_.bind(function() { + $('button#validate-order', this.$element).removeAttr('disabled'); + return currentOrder.set({ + validated: true + }); + }, this)); + }, + bindPaymentLineEvents: function() { + this.currentPaymentLines = (this.pos.get('selectedOrder')).get('paymentLines'); + this.currentPaymentLines.bind('add', this.addPaymentLine, this); + this.currentPaymentLines.bind('remove', this.renderElement, this); + this.currentPaymentLines.bind('all', this.updatePaymentSummary, this); + }, + bindOrderLineEvents: function() { + this.currentOrderLines = (this.pos.get('selectedOrder')).get('orderLines'); + this.currentOrderLines.bind('all', this.updatePaymentSummary, this); + }, + changeSelectedOrder: function() { + this.currentPaymentLines.unbind(); + this.bindPaymentLineEvents(); + this.currentOrderLines.unbind(); + this.bindOrderLineEvents(); + this.renderElement(); + }, + addPaymentLine: function(newPaymentLine) { + var x = new module.PaymentlineWidget(null, { + model: newPaymentLine + }); + x.on_delete.add(_.bind(this.deleteLine, this, x)); + x.appendTo(this.paymentLineList()); + }, + renderElement: function() { + this.paymentLineList().empty(); + this.currentPaymentLines.each(_.bind( function(paymentLine) { + this.addPaymentLine(paymentLine); + }, this)); + this.updatePaymentSummary(); + }, + deleteLine: function(lineWidget) { + this.currentPaymentLines.remove([lineWidget.model]); + }, + updatePaymentSummary: function() { + var currentOrder, dueTotal, paidTotal, remaining, remainingAmount; + currentOrder = this.pos.get('selectedOrder'); + paidTotal = currentOrder.getPaidTotal(); + dueTotal = currentOrder.getTotal(); + this.$element.find('#payment-due-total').html(dueTotal.toFixed(2)); + this.$element.find('#payment-paid-total').html(paidTotal.toFixed(2)); + remainingAmount = dueTotal - paidTotal; + remaining = remainingAmount > 0 ? 0 : (-remainingAmount).toFixed(2); + $('#payment-remaining').html(remaining); + }, + setNumpadState: function(numpadState) { + if (this.numpadState) { + this.numpadState.unbind('setValue', this.setValue); + this.numpadState.unbind('change:mode', this.setNumpadMode); + } + this.numpadState = numpadState; + if (this.numpadState) { + this.numpadState.bind('setValue', this.setValue, this); + this.numpadState.bind('change:mode', this.setNumpadMode, this); + this.numpadState.reset(); + this.setNumpadMode(); + } + }, + setNumpadMode: function() { + this.numpadState.set({mode: 'payment'}); + }, + setValue: function(val) { + this.currentPaymentLines.last().set({amount: val}); + }, + }); + + module.ReceiptWidget = instance.web.OldWidget.extend({ + init: function(parent, options) { + this._super(parent); + this.model = options.model; + this.pos = options.pos; + this.pos = options.pos; + this.user = this.pos.get('user'); + this.company = this.pos.get('company'); + this.shop_obj = this.pos.get('shop'); + }, + start: function() { + this.pos.bind('change:selectedOrder', this.changeSelectedOrder, this); + this.changeSelectedOrder(); + }, + renderElement: function() { + this.$element.html(qweb_template('pos-receipt-view')); + $('button#pos-finish-order', this.$element).click(_.bind(this.finishOrder, this)); + $('button#print-the-ticket', this.$element).click(_.bind(this.print, this)); + }, + print: function() { + window.print(); + }, + finishOrder: function() { + this.pos.get('selectedOrder').destroy(); + }, + changeSelectedOrder: function() { + if (this.currentOrderLines) + this.currentOrderLines.unbind(); + this.currentOrderLines = (this.pos.get('selectedOrder')).get('orderLines'); + this.currentOrderLines.bind('add', this.refresh, this); + this.currentOrderLines.bind('change', this.refresh, this); + this.currentOrderLines.bind('remove', this.refresh, this); + if (this.currentPaymentLines) + this.currentPaymentLines.unbind(); + this.currentPaymentLines = (this.pos.get('selectedOrder')).get('paymentLines'); + this.currentPaymentLines.bind('all', this.refresh, this); + this.refresh(); + }, + refresh: function() { + this.currentOrder = this.pos.get('selectedOrder'); + $('.pos-receipt-container', this.$element).html(qweb_template('pos-ticket')({widget:this})); + }, + }); + + module.OrderButtonWidget = instance.web.OldWidget.extend({ + tagName: 'li', + template_fct: qweb_template('pos-order-selector-button-template'), + init: function(parent, options) { + this._super(parent); + this.order = options.order; + this.pos = options.pos; + this.order.bind('destroy', _.bind( function() { + this.destroy(); + }, this)); + this.pos.bind('change:selectedOrder', _.bind( function(pos) { + var selectedOrder; + selectedOrder = pos.get('selectedOrder'); + if (this.order === selectedOrder) { + this.setButtonSelected(); + } + }, this)); + }, + start: function() { + $('button.select-order', this.$element).click(_.bind(this.selectOrder, this)); + $('button.close-order', this.$element).click(_.bind(this.closeOrder, this)); + }, + selectOrder: function(event) { + this.pos.set({ + selectedOrder: this.order + }); + }, + setButtonSelected: function() { + $('.selected-order').removeClass('selected-order'); + this.$element.addClass('selected-order'); + }, + closeOrder: function(event) { + this.order.destroy(); + }, + renderElement: function() { + this.$element.html(this.template_fct({widget:this})); + this.$element.addClass('order-selector-button'); + } + }); + + module.ActionButtonWidget = instance.web.Widget.extend({ + template:'pos-action-button', + init: function(parent, options){ + this._super(parent, options); + this.label = options.label || 'button'; + this.rightalign = options.rightalign || false; + this.click_action = options.click; + if(options.icon){ + this.icon = options.icon; + this.template = 'pos-action-button-with-icon'; + } + }, + start: function(){ + if(this.click_action){ + this.$element.click(_.bind(this.click_action, this)); + } + }, + }); + + module.ActionbarWidget = instance.web.Widget.extend({ + template:'pos-actionbar', + init: function(parent, options){ + this._super(parent,options); + this.left_button_list = []; + this.right_button_list = []; + }, + start: function(){ + console.log('hello world!'); + window.actionbarwidget = this; + }, + destroyButtons:function(position){ + var button_list; + if(position === 'left'){ + button_list = this.left_button_list; + this.left_button_list = []; + }else if (position === 'right'){ + button_list = this.right_button_list; + this.right_button_list = []; + }else{ + return this; + } + for(var i = 0; i < button_list.length; i++){ + button_list[i].destroy(); + } + return this; + }, + addNewButton: function(position,button_options){ + if(arguments.length == 2){ + var button_list; + var $button_list; + if(position === 'left'){ + button_list = this.left_button_list; + $button_list = $('.pos-actionbar-left-region'); + }else if(position === 'right'){ + button_list = this.right_button_list; + $button_list = $('.pos-actionbar-right-region'); + } + var button = new module.ActionButtonWidget(this,button_options); + button_list.push(button); + button.appendTo($button_list); + }else{ + for(var i = 1; i < arguments.length; i++){ + this.addNewButton(position,arguments[i]); + } + } + return this; + } + /* + renderElement: function() { + //this.$element.html(this.template_fct()); + },*/ + }); + + // A Widget that displays an onscreen keyboard. + // There are two options when creating the widget : + // + // * 'keyboard_model' : 'simple' | 'full' (default) + // The 'full' emulates a PC keyboard, while 'simple' emulates an 'android' one. + // + // * 'input_selector : (default: '.searchbox input') + // defines the dom element that the keyboard will write to. + // + // The widget is initially hidden. It can be shown with this.show(), and is + // automatically shown when the input_selector gets focused. + + module.OnscreenKeyboardWidget = instance.web.Widget.extend({ + tagName: 'div', + + init: function(parent, options){ + var self = this; + + this._super(parent,options); + + function get_option(opt,default_value){ + if(options){ + return options[opt] || default_value; + }else{ + return default_value; + } + } + + this.keyboard_model = get_option('keyboard_model','full'); + this.template_simple = qweb_template('pos-onscreen-keyboard-simple-template'); + this.template_full = qweb_template('pos-onscreen-keyboard-full-template'); + + this.template_fct = function(){ + if( this.keyboard_model == 'full' ){ + return this.template_full.apply(this,arguments); + }else{ + return this.template_simple.apply(this,arguments); + } + }; + + this.input_selector = get_option('input_selector','.searchbox input'); + + //show the keyboard when the input zone is clicked. + $(this.input_selector).focus(function(){self.show();}); + + //Keyboard state + this.capslock = false; + this.shift = false; + this.numlock = false; + }, + + // Write a character to the input zone + writeCharacter: function(character){ + var $input = $(this.input_selector); + $input[0].value += character; + $input.keydown(); + $input.keyup(); + }, + + // Sends a 'return' character to the input zone. TODO + sendReturn: function(){ + }, + + // Removes the last character from the input zone. + deleteCharacter: function(){ + var $input = $(this.input_selector); + var input_value = $input[0].value; + $input[0].value = input_value.substr(0, input_value.length - 1); + $input.keydown(); + $input.keyup(); + }, + + // Clears the content of the input zone. + deleteAllCharacters: function(){ + var $input = $(this.input_selector); + $input[0].value = ""; + $input.keydown(); + $input.keyup(); + }, + renderElement: function(){ + this.$element.html(this.template_fct()); + }, + + // Makes the keyboard show and slide from the bottom of the screen. + show: function(){ + $('.keyboard_frame').show().animate({'height':'235px'}, 500, 'swing'); + }, + + // Makes the keyboard hide by sliding to the bottom of the screen. + hide: function(){ + var self = this; + var frame = $('.keyboard_frame'); + frame.animate({'height':'0'}, 500, 'swing', function(){ frame.hide(); self.reset(); }); + }, + + //What happens when the shift key is pressed : toggle case, remove capslock + toggleShift: function(){ + $('.letter').toggleClass('uppercase'); + $('.symbol span').toggle(); + + self.shift = (self.shift === true) ? false : true; + self.capslock = false; + }, + + //what happens when capslock is pressed : toggle case, set capslock + toggleCapsLock: function(){ + $('.letter').toggleClass('uppercase'); + self.capslock = true; + }, + + //What happens when numlock is pressed : toggle symbols and numlock label + toggleNumLock: function(){ + $('.symbol span').toggle(); + $('.numlock span').toggle(); + self.numlock = (self.numlock === true ) ? false : true; + }, + + //After a key is pressed, shift is disabled. + removeShift: function(){ + if (self.shift === true) { + $('.symbol span').toggle(); + if (this.capslock === false) $('.letter').toggleClass('uppercase'); + + self.shift = false; + } + }, + + // Resets the keyboard to its original state; capslock: false, shift: false, numlock: false + reset: function(){ + if(this.shift){ + this.toggleShift(); + } + if(this.capslock){ + this.toggleCapsLock(); + } + if(this.numlock){ + this.toggleNumLock(); + } + }, + + //called after the keyboard is in the DOM, sets up the key bindings. + start: function(){ + var self = this; + + //this.show(); + + + $('.close_button').click(function(){ + self.deleteAllCharacters(); + self.hide(); + }); + + // Keyboard key click handling + $('.keyboard li').click(function(){ + + var $this = $(this), + character = $this.html(); // If it's a lowercase letter, nothing happens to this variable + + if ($this.hasClass('left-shift') || $this.hasClass('right-shift')) { + self.toggleShift(); + return false; + } + + if ($this.hasClass('capslock')) { + self.toggleCapsLock(); + return false; + } + + if ($this.hasClass('delete')) { + self.deleteCharacter(); + return false; + } + + if ($this.hasClass('numlock')){ + self.toggleNumLock(); + return false; + } + + // Special characters + if ($this.hasClass('symbol')) character = $('span:visible', $this).html(); + if ($this.hasClass('space')) character = ' '; + if ($this.hasClass('tab')) character = "\t"; + if ($this.hasClass('return')) character = "\n"; + + // Uppercase letter + if ($this.hasClass('uppercase')) character = character.toUpperCase(); + + // Remove shift once a key is clicked. + self.removeShift(); + + self.writeCharacter(character); + }); + }, + }); + + module.SynchNotification = instance.web.OldWidget.extend({ + template: "pos-synch-notification", + init: function() { + this._super.apply(this, arguments); + this.nbr_pending = 0; + }, + renderElement: function() { + this._super.apply(this, arguments); + $('.oe_pos_synch-notification-button', this.$element).click(this.on_synch); + }, + on_change_nbr_pending: function(nbr_pending) { + this.nbr_pending = nbr_pending; + this.renderElement(); + }, + on_synch: function() {} + }); + + module.PosWidget = instance.web.OldWidget.extend({ + init: function() { + this._super.apply(this, arguments); + this.pos = new module.PosModel(this.session); + }, + start: function() { + var self = this; + return self.pos.ready.then(_.bind(function() { + this.renderElement(); + this.synch_notification = new module.SynchNotification(this); + this.synch_notification.replace($('.oe_pos_synch-notification', this.$element)); + this.synch_notification.on_synch.add(_.bind(self.pos.flush, self.pos)); + + self.pos.bind('change:nbr_pending_operations', this.changed_pending_operations, this); + this.changed_pending_operations(); + + this.$element.find("#loggedas button").click(function() { + self.try_close(); + }); + + this.buildWidgets(); + + //self.pos.app = new module.App(self.$element, self.pos); + + //this.shopView = new module.ShopWidget(null, { 'pos': this.pos } ); + //this.shopView.$element = self.$element; + //this.shopView.start(); + + + + instance.webclient.set_content_full_screen(true); + + if (self.pos.get('bank_statements').length === 0) + return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_open_statement']], ['res_id']).pipe( + _.bind(function(res) { + return this.rpc('/web/action/load', {'action_id': res[0]['res_id']}).pipe(_.bind(function(result) { + var action = result.result; + this.do_action(action); + }, this)); + }, this)); + }, this)); + }, + render: function() { + return qweb_template("POSWidget")(); + }, + buildWidgets: function() { + var self = this; + $('button#neworder-button', this.$element).click(_.bind(this.createNewOrder, this)); + + (this.pos.get('orders')).bind('add', this.orderAdded, this); + (this.pos.get('orders')).add(new module.Order({'pos':this.pos})); + this.productListView = new module.ProductListWidget(null, { + pos: this.pos, + }); + this.productListView.$element = $("#products-screen-ol"); + this.productListView.renderElement(); + this.productListView.start(); + this.paypadView = new module.PaypadWidget(null, { + pos: this.pos + }); + this.paypadView.$element = $('#paypad'); + this.paypadView.renderElement(); + this.paypadView.start(); + this.numpadView = new module.NumpadWidget(null); + this.numpadView.$element = $('#numpad'); + this.numpadView.start(); + this.orderView = new module.OrderWidget(null, { + pos: this.pos, + }); + this.orderView.$element = $('#current-order-content'); + this.orderView.start(); + this.paymentView = new module.PaymentWidget(null, { + pos: this.pos, + }); + this.paymentView.$element = $('#payment-screen'); + this.paymentView.renderElement(); + this.paymentView.start(); + this.receiptView = new module.ReceiptWidget(null, { + pos: this.pos, + }); + this.receiptView.replace($('#receipt-screen')); + this.stepSwitcher = new module.StepSwitcher(this, {pos: this.pos}); + this.pos.bind('change:selectedOrder', this.changedSelectedOrder, this); + this.changedSelectedOrder(); + + this.categoryView = new module.CategoryWidget(null, { + 'element_id': 'products-screen-categories', + 'pos': this.pos, + }); + this.categoryView.on_change_category.add_last(_.bind(this.search_and_categories, this)); + this.search_and_categories(); + + this.onscreenKeyboard = new module.OnscreenKeyboardWidget(null, { + 'keyboard_model': 'simple' + }); + this.onscreenKeyboard.appendTo($(".point-of-sale #content")); + + this.barcodeReader = new module.BarcodeReader({'pos': self.pos }); + + //this.connection = new instance.web.Connection(); + //this.connection.session_bind('http://localhost:8069'); + this.connection = new instance.web.BaseConnection(); + this.connection.bind('http://localhost:8069'); + + + this.actionBar = new module.ActionbarWidget(null); + this.actionBar.appendTo($(".point-of-sale #content")); + this.actionBar.addNewButton('left',{ + label : 'Hello World', + icon : '/point_of_sale/static/src/img/icons/png48/face-monkey.png', + click : function() { + + //console.log("Hello World!"); + self.connection.rpc("/pos/dispatch", {iface: 'light', status: 1}, function(result) { + console.log(result); + }) + } + }); + + }, + createNewOrder: function() { + var newOrder; + newOrder = new module.Order({'pos': this.pos}); + (this.pos.get('orders')).add(newOrder); + this.pos.set({ + selectedOrder: newOrder + }); + }, + orderAdded: function(newOrder) { + var newOrderButton; + newOrderButton = new module.OrderButtonWidget(null, { + order: newOrder, + pos: this.pos + }); + newOrderButton.appendTo($('#orders')); + newOrderButton.selectOrder(); + }, + changedSelectedOrder: function() { + if (this.currentOrder) { + this.currentOrder.unbind('change:step', this.changedStep); + } + this.currentOrder = this.pos.get('selectedOrder'); + this.currentOrder.bind('change:step', this.changedStep, this); + this.changedStep(); + }, + changedStep: function() { + var step = this.currentOrder.get('step'); + this.orderView.setNumpadState(null); + this.paymentView.setNumpadState(null); + if (step === 'products') { + this.orderView.setNumpadState(this.numpadView.state); + } else if (step === 'payment') { + this.paymentView.setNumpadState(this.numpadView.state); + } + }, + search_and_categories: function(id){ + var self = this, + c, + product_list, + allProducts, + allPackages; + + id = id || 0; + + c = this.pos.categories[id]; + this.categoryView.ancestors = c.ancestors; + this.categoryView.children = c.children; + this.categoryView.renderElement(); + this.categoryView.start(); + + allProducts = this.pos.get('product_list'); + + allPackages = this.pos.get('product.packaging'); + + product_list = this.pos.get('product_list').filter( function(p){ + var _ref = p.pos_categ_id[0]; + return _.indexOf(c.subtree, _ref) >= 0; + }); + + this.pos.get('products').reset(product_list); + + $('.searchbox input').keyup(function(){ + var results, search_str; + search_str = $(this).val().toLowerCase(); + if(search_str){ + results = product_list.filter( function(p){ + return p.name.toLowerCase().indexOf(search_str) != -1; + }); + $('.search-clear').fadeIn(); + }else{ + results = product_list; + $('.search-clear').fadeOut(); + } + self.pos.get('products').reset(results); + }); + + $('.search-clear').click(function(){ + self.pos.get('products').reset(product_list); + $('.searchbox input').val('').focus(); + $('.search-clear').fadeOut(); + }); + }, + changed_pending_operations: function () { + var self = this; + this.synch_notification.on_change_nbr_pending(self.pos.get('nbr_pending_operations').length); + }, + try_close: function() { + var self = this; + self.pos.flush().then(_.bind(function() { + var close = _.bind(this.close, this); + if (self.pos.get('nbr_pending_operations').length > 0) { + var confirm = false; + $(QWeb.render('pos-close-warning')).dialog({ + resizable: false, + height:160, + modal: true, + title: "Warning", + buttons: { + "Yes": function() { + confirm = true; + $( this ).dialog( "close" ); + }, + "No": function() { + $( this ).dialog( "close" ); + } + }, + close: function() { + if (confirm) + close(); + } + }); + } else { + close(); + } + }, this)); + }, + close: function() { + this.barcodeReader.disconnect(); + + return new instance.web.Model("ir.model.data").get_func("search_read")([['name', '=', 'action_pos_close_statement']], ['res_id']).pipe( + _.bind(function(res) { + return this.rpc('/web/action/load', {'action_id': res[0]['res_id']}).pipe(_.bind(function(result) { + var action = result.result; + action.context = _.extend(action.context || {}, {'cancel_action': {type: 'ir.actions.client', tag: 'default_home'}}); + this.do_action(action); + }, this)); + }, this)); + }, + destroy: function() { + instance.webclient.set_content_full_screen(false); + self.pos = undefined; + this._super(); + } + }); +} diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index d24d16713d8..eb8f8df3dec 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -2,7 +2,7 @@ - +
@@ -141,6 +141,9 @@ + +
The product could not be recognized. Please contact an employee.
+
There are pending operations that could not be saved into the database, are you sure you want to exit?
@@ -291,4 +294,138 @@
+ +
+
    + +
+ +
    + +
+
+
+ +
  • +
    + +
    +
  • +
    + +
  • +
    + +
    +
    +
  • +
    + + +
    +
      +
    • `~
    • +
    • 1!
    • +
    • 2@
    • +
    • 3#
    • +
    • 4$
    • +
    • 5%
    • +
    • 6^
    • +
    • 7&
    • +
    • 8*
    • +
    • 9(
    • +
    • 0)
    • +
    • -_
    • +
    • =+
    • +
    • delete
    • +
    • tab
    • +
    • q
    • +
    • w
    • +
    • e
    • +
    • r
    • +
    • t
    • +
    • y
    • +
    • u
    • +
    • i
    • +
    • o
    • +
    • p
    • +
    • [{
    • +
    • ]}
    • +
    • \|
    • +
    • caps lock
    • +
    • a
    • +
    • s
    • +
    • d
    • +
    • f
    • +
    • g
    • +
    • h
    • +
    • j
    • +
    • k
    • +
    • l
    • +
    • ;:
    • +
    • '"
    • +
    • return
    • +
    • shift
    • +
    • z
    • +
    • x
    • +
    • c
    • +
    • v
    • +
    • b
    • +
    • n
    • +
    • m
    • +
    • ,<
    • +
    • .>
    • +
    • /?
    • +
    • shift
    • +
    • &nbsp;
    • +
    +

    close

    +
    +
    + +
    +
      +
    • q1
    • +
    • w2
    • +
    • e3
    • +
    • r4
    • +
    • t5
    • +
    • y6
    • +
    • u7
    • +
    • i8
    • +
    • o9
    • +
    • p0
    • + +
    • a@
    • +
    • s#
    • +
    • d%
    • +
    • f*
    • +
    • g/
    • +
    • h-
    • +
    • j+
    • +
    • k(
    • +
    • l)
    • + +
    • z?
    • +
    • x!
    • +
    • c"
    • +
    • v'
    • +
    • b:
    • +
    • n;
    • +
    • m,
    • +
    • delete
    • + +
    • 123ABC
    • +
    • &nbsp;
    • +
    • ..
    • +
    • return
    • +
    +

    close

    +
    +
    diff --git a/addons/point_of_sale/wizard/pos_box_entries.xml b/addons/point_of_sale/wizard/pos_box_entries.xml index 68520ff2d0c..7601ba97632 100644 --- a/addons/point_of_sale/wizard/pos_box_entries.xml +++ b/addons/point_of_sale/wizard/pos_box_entries.xml @@ -36,6 +36,14 @@ key2="client_action_multi" id="action_box_entries" /> + + diff --git a/addons/point_of_sale/wizard/pos_box_out.xml b/addons/point_of_sale/wizard/pos_box_out.xml index a374b2fb121..3f34a93c39e 100644 --- a/addons/point_of_sale/wizard/pos_box_out.xml +++ b/addons/point_of_sale/wizard/pos_box_out.xml @@ -36,5 +36,13 @@ key2="client_action_multi" id="action_box_out" /> + + diff --git a/addons/product/product.py b/addons/product/product.py index 70f6a0683d6..c3ef6d6c688 100644 --- a/addons/product/product.py +++ b/addons/product/product.py @@ -518,7 +518,7 @@ class product_product(osv.osv): 'active': fields.boolean('Active', help="If the active field is set to False, it will allow you to hide the product without removing it."), 'variants': fields.char('Variants', size=64), 'product_tmpl_id': fields.many2one('product.template', 'Product Template', required=True, ondelete="cascade"), - 'ean13': fields.char('EAN13', size=13), + 'ean13': fields.char('EAN13', size=13, help='Barcode'), 'packaging' : fields.one2many('product.packaging', 'product_id', 'Logistical Units', help="Gives the different ways to package the same product. This has no impact on the picking order and is mainly used if you use the EDI module."), 'price_extra': fields.float('Variant Price Extra', digits_compute=dp.get_precision('Sale Price')), 'price_margin': fields.float('Variant Price Margin', digits_compute=dp.get_precision('Sale Price')),