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 @@
-
+