From 7961aee994a2b0be26fad786c6a16c98e12aa2df Mon Sep 17 00:00:00 2001 From: Julien Moutinho Date: Wed, 3 Apr 2013 09:20:27 +0200 Subject: [PATCH] Ajout : point_of_sale.patch . --- point_of_sale.patch | 1258 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1258 insertions(+) create mode 100644 point_of_sale.patch diff --git a/point_of_sale.patch b/point_of_sale.patch new file mode 100644 index 0000000..d8be7a1 --- /dev/null +++ b/point_of_sale.patch @@ -0,0 +1,1258 @@ +diff --git a/controllers/main.py b/controllers/main.py +index b189c8f..d3743ac 100644 +--- a/controllers/main.py ++++ b/controllers/main.py +@@ -29,7 +29,6 @@ class PointOfSaleController(openerp.addons.web.http.Controller): + def manifest(self, req, **kwargs): + """ This generates a HTML5 cache manifest files that preloads the categories and products thumbnails + and other ressources necessary for the point of sale to work offline """ +- + ml = ["CACHE MANIFEST"] + + # loading all the images in the static/src/img/* directories +@@ -56,10 +55,17 @@ class PointOfSaleController(openerp.addons.web.http.Controller): + category_id = c['id'] + url = "/web/binary/image?session_id=%s&model=pos.category&field=image&id=%s" % (req.session_id, category_id) + ml.append(url) ++ ++ partners = req.session.model('res.partner') ++ for c in partners.search_read([],['name']): ++ partner_id = c['id'] ++ url = "/web/binary/image?session_id=%s&model=res.partner&field=image&id=%s" % (req.session_id, partner_id) ++ ml.append(url) + + ml += ["NETWORK:","*"] + m = "\n".join(ml) + ++ print ("DEV: [point_of_sale] [controllers] [main] return=%s" % m) + return m + + @openerp.addons.web.http.jsonrequest +diff --git a/point_of_sale.py b/point_of_sale.py +index 7e8487b..371a07b 100644 +--- a/point_of_sale.py ++++ b/point_of_sale.py +@@ -479,8 +479,22 @@ class pos_order(osv.osv): + _description = "Point of Sale" + _order = "id desc" + ++ def create_partner_from_ui(self, cr, uid, partners, context=None): ++ print ("DEV: [point_of_sale] [create_partner_from_ui] partners=%s" % str(partners)) ++ partner_ids = [] ++ partner_obj = self.pool.get('res.partner') ++ for tmp_partner in partners: ++ partner = tmp_partner['data'] ++ partner_id = partner_obj.create(cr, uid, { ++ 'name': partner['name'], ++ }, context) ++ partner_ids.append(partner_id) ++ #self.signal_paid(cr, uid, [partner_id]) ++ return partner_ids ++ + def create_from_ui(self, cr, uid, orders, context=None): + #_logger.info("orders: %r", orders) ++ print ("DEV: [point_of_sale] [create_from_ui] order=", str(orders)) + order_ids = [] + for tmp_order in orders: + order = tmp_order['data'] +@@ -489,7 +503,8 @@ class pos_order(osv.osv): + 'user_id': order['user_id'] or False, + 'session_id': order['pos_session_id'], + 'lines': order['lines'], +- 'pos_reference':order['name'] ++ 'pos_reference':order['name'], ++ 'partner_id':order['partner_id'], + }, context) + + for payments in order['statement_ids']: +diff --git a/static/src/css/pos.css b/static/src/css/pos.css +index d9a4d6d..9357d5d 100644 +--- a/static/src/css/pos.css ++++ b/static/src/css/pos.css +@@ -619,6 +619,200 @@ + padding-top:15px; + } + ++/* ********* The partner list ********* */ ++ ++.point-of-sale .partner-list { ++ padding:10px !important; ++} ++ ++.point-of-sale .partner-list-scroller{ ++ -webkit-box-sizing: border-box; ++ -moz-box-sizing: border-box; ++ -ms-box-sizing: border-box; ++ box-sizing: border-box; ++ width:100%; ++ height:100%; ++ overflow: hidden; ++} ++.point-of-sale .partner-list-container { ++ position:absolute; ++ top:0px; ++ bottom:0px; ++ left:0px; ++ right:0px; ++} ++ ++/* a) the search box */ ++ ++.point-of-sale #partners-screen .searchbox { ++ position: absolute; ++ right: 2px; ++} ++.point-of-sale #partners-screen .searchbox input { ++ width: 130px; ++ border-radius: 11px; ++ border: 1px solid #cecbcb; ++ padding: 3px 19px; ++ margin: 6px; ++ background: url("../img/search.png") no-repeat 5px; ++ background-color: white; ++} ++.point-of-sale #partners-screen .search-clear { ++ position: absolute; ++ top: 11px; ++ right: 11px; ++ cursor: pointer; ++ display: none; ++} ++ ++.point-of-sale #partners-screen .search-create { ++ position: absolute; ++ top: 6px; ++ right: 22px; ++ cursor: pointer; ++ display: none; ++} ++ ++ ++/* b) the partner */ ++ ++.point-of-sale .partner { ++ position:relative; ++ vertical-align: top; ++ display: inline-block; ++ line-height: 100px; ++ font-size: 11px; ++ margin: 5px !important; ++ width: 120px; ++ height:120px; ++ background:#fff; ++ border: 1px solid #fff; ++ border-radius: 3px; ++ -webkit-box-shadow: 0px 2px 0px #dad8e4, 0px 1px 8px #636480; ++ -moz-box-shadow: 0px 2px 0px #dad8e4, 0px 1px 8px #636480; ++ box-shadow: 0px 2px 0px #dad8e4, 0px 1px 8px #636480; ++} ++ ++.point-of-sale .partner .partner-img { ++ position: relative; ++ width: 120px; ++ height: 100px; ++ background: white; ++ text-align: center; ++} ++ ++.point-of-sale .partner .partner-img img { ++ max-height: 100px; ++ max-width: 120px; ++} ++ ++.point-of-sale .partner .price-tag { ++ position: absolute; ++ top: 2px; ++ right: 2px; ++ vertical-align: top; ++ color: white; ++ line-height: 13px; ++ background: #7f82ac; ++ padding: 2px 5px; ++ border-radius: 3px; ++ box-shadow: 0px 1px 0px #9A9CC5, 0px 3px 0px #7E86AC, 0px 3px 3px rgba(12, 14, 68, 0.67); ++} ++ ++.point-of-sale .partner .partner-name { ++ position: absolute; ++ -webkit-box-sizing: border-box; ++ -moz-box-sizing: border-box; ++ -ms-box-sizing: border-box; ++ box-sizing: border-box; ++ bottom:0; ++ top:auto; ++ line-height: 14px; ++ width:100%; ++ background: -webkit-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1)); ++ background: -moz-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1)); ++ background: -ms-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1)); ++ /* troublesome in latest webkit ++ background: linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1)); ++ */ ++ /*background:#FFF;*/ ++ padding: 3px; ++ padding-top:15px; ++} ++ ++/* c) the partner creation */ ++ ++.point-of-sale .pos-step-container { ++ display: inline-block; ++ font-size: 1.5em; ++} ++.point-of-sale .greyed-out{ ++ color: #AAA; ++} ++.point-of-sale .pos-step-container input{ ++ font-size: 1em; ++ outline: none; ++ border: none; ++ padding: 0px 8px; ++ padding-top: 8px; ++ margin-left: 16px; ++ border-radius: 5px; ++ background: white; ++ box-shadow: 0px -1px 0px #E2E2E2 inset,0px 1px 0px white inset, 0px 4px 0px #DDD inset, 0px 4px 8px rgba(0, 0, 0, 0.55) inset; ++ color: #4c4c4c; ++ -webkit-animation: all 250ms linear; ++} ++ ++.point-of-sale .pos-step-container input:focus{ ++ box-shadow: 0px -1px 0px #C9CFFD inset,0px 1px 0px #B8C8FC inset, 0px 4px 0px #9FD5FF inset, 0px 4px 9px rgba(0, 31, 255, 0.55) inset; ++ color: #5d7ad6; ++ -webkit-animation: all 250ms linear; ++} ++ ++.point-of-sale .pos-partner-create-container { ++ text-align: left; ++ min-width: 500px; ++ margin-top: 50px; ++ padding: 40px; ++ background-color: #f8f8f8; ++ border-radius: 4px; ++ box-shadow: 0px 1px 0px white,0px -1px 0px white, 0px 4px 0px #DFDFDF, 0px 10px 30px rgba(0, 0, 0, 0.21); ++} ++.point-of-sale .pos-partner-create-container .left-block{ ++ display: inline-block; ++ width:49%; ++ margin:0; ++ padding:0; ++ text-align:left; ++} ++.point-of-sale .pos-partner-create-container .header{ ++ margin-top: 0px; ++ margin-bottom:20px; ++ font-weight: bold; ++} ++.point-of-sale .pos-partner-create-container .infoline{ ++ margin-top:5px; ++ margin-bottom:5px; ++} ++.point-of-sale .pos-partner-create-container .right-block{ ++ display: inline-block; ++ width:49%; ++ margin:0; ++ padding:0; ++ text-align:right; ++} ++.point-of-sale .pos-partner-create-container table { ++ width: 100%; ++ margin-bottom: 20px; ++} ++.point-of-sale .pos-partner-create-container td { ++ vertical-align: middle; ++} ++.point-of-sale .pos-partner-create-container .partner-createline-type { ++ font-size: 1em; ++ font-weight: bold; ++ margin-right:10px; ++} + + /* ********* The Screens ********* */ + +diff --git a/static/src/css/pos_nohover.css b/static/src/css/pos_nohover.css +index 6f24abd..7dcb4f0 100644 +--- a/static/src/css/pos_nohover.css ++++ b/static/src/css/pos_nohover.css +@@ -579,6 +579,175 @@ + padding-top:15px; + } + ++/* ********* The partner list ********* */ ++ ++.point-of-sale .partner-list { ++ padding:10px; ++} ++ ++.point-of-sale .partner-list-scroller{ ++ -webkit-box-sizing: border-box; ++ -moz-box-sizing: border-box; ++ -ms-box-sizing: border-box; ++ box-sizing: border-box; ++ width:100%; ++ height:100%; ++ overflow: hidden; ++} ++.point-of-sale .partner-list-container { ++ position:absolute; ++ top:0px; ++ bottom:0px; ++ left:0px; ++ right:0px; ++ background: #eeedff; ++} ++ ++/* a) the search box */ ++ ++.point-of-sale #partners-screen .searchbox { ++ position: absolute; ++ right: 2px; ++} ++.point-of-sale #partners-screen .searchbox input { ++ width: 130px; ++ border-radius: 11px; ++ border: 1px solid #cecbcb; ++ padding: 3px 19px; ++ margin: 6px; ++ background: url("../img/search.png") no-repeat 5px; ++ background-color: white; ++} ++.point-of-sale #partners-screen .search-clear { ++ position: absolute; ++ top: 11px; ++ right: 11px; ++ cursor: pointer; ++ display: none; ++} ++.point-of-sale #partners-screen .search-create { ++ position: absolute; ++ top: 6px; ++ right: 22px; ++ cursor: pointer; ++ display: none; ++} ++ ++/* b) the partner */ ++ ++.point-of-sale .partner { ++ position:relative; ++ vertical-align: top; ++ display: inline-block; ++ line-height: 100px; ++ font-size: 11px; ++ margin: 5px; ++ width: 120px; ++ height:120px; ++ background:#fff; ++ border: 1px solid #fff; ++ border-radius: 3px; ++ -webkit-box-shadow: 0px 1px 8px rgba(127,130,172,0.4); ++ -moz-box-shadow: 0px 1px 8px rgba(127,130,172,0.4); ++ box-shadow: 0px 1px 8px rgba(127,130,172,0.4); ++} ++ ++.point-of-sale .partner .partner-img { ++ position: relative; ++ width: 120px; ++ height: 100px; ++ background: white; ++ text-align: center; ++} ++ ++.point-of-sale .partner .partner-img img { ++ max-height: 100px; ++ max-width: 120px; ++} ++ ++.point-of-sale .partner .price-tag { ++ position: absolute; ++ top: 2px; ++ right: 2px; ++ vertical-align: top; ++ color: white; ++ line-height: 14px; ++ background: #7f82ac; ++ padding: 2px 5px; ++ border-radius: 3px; ++} ++ ++.point-of-sale .partner .partner-name { ++ position: absolute; ++ -webkit-box-sizing: border-box; ++ -moz-box-sizing: border-box; ++ -ms-box-sizing: border-box; ++ box-sizing: border-box; ++ bottom:0; ++ top:auto; ++ line-height: 14px; ++ width:100%; ++ background: -webkit-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1)); ++ background: -moz-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1)); ++ background: -ms-linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1)); ++ background: linear-gradient(-90deg,rgba(255,255,255,0),rgba(255,255,255,1), rgba(255,255,255,1)); ++ /*background:#FFF;*/ ++ padding: 3px; ++ padding-top:15px; ++} ++ ++/* c) the partner creation */ ++ ++.point-of-sale .pos-step-container { ++ display: inline-block; ++ font-size: 1.5em; ++} ++.point-of-sale .greyed-out{ ++ color: #AAA; ++} ++.point-of-sale .pos-step-container input{ ++ font-size: 1em; ++} ++ ++.point-of-sale .pos-partner-create-container { ++ text-align: left; ++ min-width: 500px; ++} ++.point-of-sale .pos-partner-create-container .left-block{ ++ display: inline-block; ++ width:49%; ++ margin:0; ++ padding:0; ++ text-align:left; ++} ++.point-of-sale .pos-partner-create-container .header{ ++ margin-top: 50px; ++ margin-bottom:20px; ++ font-weight: bold; ++} ++.point-of-sale .pos-partner-create-container .infoline{ ++ margin-top:5px; ++ margin-bottom:5px; ++} ++.point-of-sale .pos-partner-create-container .right-block{ ++ display: inline-block; ++ width:49%; ++ margin:0; ++ padding:0; ++ text-align:right; ++} ++.point-of-sale .pos-partner-create-container table { ++ width: 100%; ++ margin-bottom: 20px; ++} ++.point-of-sale .pos-partner-create-container td { ++ vertical-align: middle; ++} ++.point-of-sale .pos-partner-create-container .partner-createline-type { ++ font-size: 1em; ++ font-weight: bold; ++ margin-right:10px; ++} + + /* ********* The Screens ********* */ + +diff --git a/static/src/js/db.js b/static/src/js/db.js +index a79b111..93c71e9 100644 +--- a/static/src/js/db.js ++++ b/static/src/js/db.js +@@ -40,6 +40,9 @@ function openerp_pos_db(instance, module){ + //cache the data in memory to avoid roundtrips to the localstorage + this.cache = {}; + ++ this.partner_search_string = ''; ++ this.partner_by_id = {}; ++ this.partner_list = []; + this.product_by_id = {}; + this.product_by_ean13 = {}; + this.product_by_category_id = {}; +@@ -196,6 +199,23 @@ function openerp_pos_db(instance, module){ + } + } + }, ++ _partner_search_string: function(partner){ ++ var str = '' + partner.id + ':' + partner.name; ++ return str + '\n'; ++ }, ++ add_partners: function(partners){ ++ if(!partners instanceof Array){ ++ partners = [partners]; ++ } ++ for(var i = 0, len = partners.length; i < len; i++){ ++ var partner = partners[i]; ++ //console.log("[db] [add_partners] partner.name:",partner.name); ++ this.partner_by_id[partner.id] = partner; ++ this.partner_list.push(partner); ++ this.partner_search_string += this._partner_search_string(partner); ++ } ++ //console.log("[db] [add_partners] partner_search_string:", this.partner_search_string); ++ }, + add_packagings: function(packagings){ + for(var i = 0, len = packagings.length; i < len; i++){ + var pack = packagings[i]; +@@ -225,6 +245,9 @@ function openerp_pos_db(instance, module){ + } + return count; + }, ++ get_partner_by_id: function(id){ ++ return this.partner_by_id[id]; ++ }, + get_product_by_id: function(id){ + return this.product_by_id[id]; + }, +@@ -266,13 +289,48 @@ function openerp_pos_db(instance, module){ + } + return results; + }, ++ search_partner: function(query){ ++ var re = RegExp("([0-9]+):.*?"+query,"gi"); ++ var results = []; ++ //console.log("[db] [search_partner] query:",query); ++ for(var i = 0; i < this.limit; i++){ ++ r = re.exec(this.partner_search_string); ++ if(r){ ++ var id = Number(r[1]); ++ //console.log("[db] [search_partner] id:",id); ++ results.push(this.get_partner_by_id(id)); ++ }else{ ++ break; ++ } ++ } ++ return results; ++ }, ++ get_partner_list: function(){ ++ return this.partner_list; ++ }, ++ add_partner: function(partner){ ++ console.log("[PosLS] [add_partner] partner=", partner.name); ++ var last_id = this.load('last_partner_id',0); ++ var partners = this.load('partners',[]); ++ partners.push({id: last_id + 1, data: partner}); ++ this.save('last_partner_id',last_id+1); ++ this.save('partners',partners); ++ }, + add_order: function(order){ ++ console.log("[PosLS] [add_order] order=", order); + var last_id = this.load('last_order_id',0); + var orders = this.load('orders',[]); + orders.push({id: last_id + 1, data: order}); + this.save('last_order_id',last_id+1); + this.save('orders',orders); + }, ++ remove_partner: function(partner_id){ ++ var partners = this.load('partners',[]); ++ partners = _.filter(partners, function(partner){ ++ return partner.id !== partner_id; ++ }); ++ this.save('partners',partners); ++ }, + remove_order: function(order_id){ + var orders = this.load('orders',[]); + orders = _.filter(orders, function(order){ +@@ -280,6 +338,9 @@ function openerp_pos_db(instance, module){ + }); + this.save('orders',orders); + }, ++ get_partners: function(){ ++ return this.load('partners',[]); ++ }, + get_orders: function(){ + return this.load('orders',[]); + }, +diff --git a/static/src/js/models.js b/static/src/js/models.js +index d2cf15b..409f193 100644 +--- a/static/src/js/models.js ++++ b/static/src/js/models.js +@@ -41,7 +41,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal + this.barcode_reader = new module.BarcodeReader({'pos': this}); // used to read barcodes + this.proxy = new module.ProxyDevice(); // used to communicate to the hardware devices via a local proxy + this.db = new module.PosLS(); // a database used to store the products and categories +- this.db.clear('products','categories'); ++ this.db.clear('products','categories','partners'); + this.debug = jQuery.deparam(jQuery.param.querystring()).debug !== undefined; //debug mode + + // default attributes values. If null, it will be loaded below. +@@ -59,6 +59,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal + 'orders': new module.OrderCollection(), + //this is the product list as seen by the product list widgets, it will change based on the category filters + 'products': new module.ProductCollection(), ++ 'partners': new module.PartnerCollection(), + 'cashRegisters': null, + + 'bank_statements': null, +@@ -69,6 +70,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal + 'units_by_id': null, + + 'selectedOrder': null, ++ 'selectedPartner': null, + }); + + this.get('orders').bind('remove', function(){ self.on_removed_order(); }); +@@ -144,6 +146,10 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal + }).then(function(partners){ + self.set('partner_list',partners); + ++ return self.fetch('res.partner', ['name']); ++ }).then(function(partners){ ++ self.db.add_partners(partners); ++ + return self.fetch('account.tax', ['amount', 'price_include', 'type']); + }).then(function(taxes){ + self.set('taxes', taxes); +@@ -256,12 +262,23 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal + } + }, + ++ push_partner: function(partner) { ++ console.log("[PosModel] [push_partner] partner=", partner.name); ++ this.db.add_partner(partner); ++ this.flush_partner(); ++ }, + // saves the order locally and try to send it to the backend. 'record' is a bizzarely defined JSON version of the Order + push_order: function(record) { + this.db.add_order(record); + this.flush(); + }, + ++ add_new_partner: function(attr){ ++ var partner = new module.PartnerCreate({pos:this, name:attr.name}); ++ console.log("[PosModel] [add_new_partner] partner=", partner.get('name')); ++ this.get('partners').add(partner); ++ this.set('selectedPartner', partner); ++ }, + //creates a new empty order and sets it as the current order + add_new_order: function(){ + var order = new module.Order({pos:this}); +@@ -269,6 +286,16 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal + this.set('selectedOrder', order); + }, + ++ flush_partner: function() { ++ //TODO make the mutex work ++ //this makes sure only one _int_flush is called at the same time ++ /* ++ return this.flush_mutex.exec(_.bind(function() { ++ return this._flush_partner(0); ++ }, this)); ++ */ ++ this._flush_partner(0); ++ }, + // attemps to send all pending orders ( stored in the pos_db ) to the server, + // and remove the successfully sent ones from the db once + // it has been confirmed that they have been sent correctly. +@@ -282,6 +309,40 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal + */ + this._flush(0); + }, ++ _flush_partner: function(index){ ++ var self = this; ++ var partners = this.db.get_partners(); ++ self.set('nbr_pending_operations',partners.length); ++ ++ var partner = partners[index]; ++ console.log("[PosModel] [_flush_partner] index=", index); ++ if(!partner){ ++ return; ++ } ++ console.log("[PosModel] [_flush_partner] partner=", partner.data.name); ++ //try to push a partner to the server ++ return (new instance.web.Model('pos.order')).get_func('create_partner_from_ui')([partner]) ++ .fail(function(unused, event){ ++ //don't show error popup if it fails ++ event.preventDefault(); ++ console.error('Failed to send partner:',partner); ++ self._flush_partner(index+1); ++ }) ++ .done(function(args){ ++ //remove from db if success ++ console.log("[PosModel] [_flush_partner] [.done] args=", args); ++ self.db.remove_partner(partner.id); ++ var name = partner.data.name; ++ p = {name:name, id:args[0]}; ++ console.log("[PosModel] [_flush_partner] [.done] partner=", p); ++ self.db.add_partners([p]); ++ //self.set('selectedPartner', p); ++ //console.log("[PosModel] [_flush_partner] [.done] selectedPartner=", self.get('selectedPartner').name); ++ //console.log("[PosModel] [_flush_partner] [.done] selectedPartner.id=", self.get('selectedPartner').id); ++ self.get('selectedOrder').addPartner(p); // NOTE: set .id asynchronously.. ++ self._flush_partner(index); ++ }); ++ }, + // attempts to send an order of index 'index' in the list of order to send. The index + // is used to skip orders that failed. do not call this method outside the mutex provided + // by flush() +@@ -295,6 +356,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal + return; + } + //try to push an order to the server ++ console.log("[PosModel] [_flush] order=", order); + (new instance.web.Model('pos.order')).get_func('create_from_ui')([order]) + .fail(function(unused, event){ + //don't show error popup if it fails +@@ -341,10 +403,22 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal + return instance.session.url('/web/binary/image', {model: 'product.product', field: 'image', id: this.get('id')}); + }, + }); ++ module.Partner = Backbone.Model.extend({ ++ initialize: function(attr, options) { ++ this.name = attr.name; ++ this.id = attr.id; ++ }, ++ get_image_url: function(){ ++ return instance.session.url('/web/binary/image', {model: 'res.partner', field: 'image', id: this.get('id')}); ++ }, ++ }); + + module.ProductCollection = Backbone.Collection.extend({ + model: module.Product, + }); ++ module.PartnerCollection = Backbone.Collection.extend({ ++ model: module.Partner, ++ }); + + // An orderline represent one element of the content of a client's shopping cart. + // An orderline contains a product, its quantity, its price, discount. etc. +@@ -598,6 +672,21 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal + }); + + ++ module.PartnerCreate = Backbone.Model.extend({ ++ initialize: function(attr){ ++ Backbone.Model.prototype.initialize.apply(this, arguments); ++ this.pos = attr.pos; ++ this.name = attr.name; ++ return this; ++ }, ++ exportAsJSON: function() { ++ console.log("[PartnerCreate] [exportAsJSON] name=",this.name); ++ return { ++ name: this.name, ++ }; ++ }, ++ }); ++ + // An order more or less represents the content of a client's shopping cart (the OrderLines) + // plus the associated payment information (the PaymentLines) + // there is always an active ('selected') order in the Pos, a new one is created +@@ -643,6 +732,11 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal + } + this.selectLine(this.getLastOrderline()); + }, ++ addPartner: function(partner, options){ ++ options = options || {}; ++ console.log("[Order] [addPartner] partner=", partner.name, " partner_id=", partner.id); ++ this.set_client(partner); ++ }, + removeOrderline: function( line ){ + this.get('orderLines').remove(line); + this.selectLine(this.getLastOrderline()); +@@ -747,6 +841,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal + var company = this.pos.get('company'); + var shop = this.pos.get('shop'); + var date = new Date(); ++ console.log("[Order] [export_for_printing] this.get('client')=",this.get('client')); + + return { + orderlines: orderlines, +@@ -804,7 +899,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal + lines: orderLines, + statement_ids: paymentLines, + pos_session_id: this.pos.get('pos_session').id, +- partner_id: this.pos.get('client') ? this.pos.get('client').id : undefined, ++ partner_id: this.get('client') ? this.get('client').id : undefined, + user_id: this.pos.get('cashier') ? this.pos.get('cashier').id : this.pos.get('user').id, + }; + }, +diff --git a/static/src/js/screens.js b/static/src/js/screens.js +index eca4a3c..eb1986a 100644 +--- a/static/src/js/screens.js ++++ b/static/src/js/screens.js +@@ -729,10 +729,54 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa + }, + }); + ++ module.PartnerScreenWidget = module.ScreenWidget.extend({ ++ template:'PartnerScreenWidget', ++ ++ next_screen: 'products', ++ ++ show_numpad: false, ++ show_leftpane: false, ++ init: function(parent, options) { ++ this._super(parent,options); ++ this.model = options.model; ++ // TODO: this.pos.bind('change:selectedOrder', this.change_selected_order, this); ++ }, ++ ++ start: function(){ ++ var self = this; ++ ++ this.partner_search_widget = new module.PartnerSearchWidget(this,{}); ++ this.partner_search_widget.replace($('.placeholder-PartnerSearchWidget')); ++ ++ this.partner_list_widget = new module.PartnerListWidget(this,{ ++ click_partner_action: function(partner){ ++ self.pos.get('selectedOrder').addPartner(partner); ++ self.pos_widget.screen_selector.set_current_screen(self.next_screen); ++ }, ++ }); ++ this.partner_list_widget.replace($('.placeholder-PartnerListWidget')); ++ }, ++ ++ show: function(){ ++ this._super(); ++ var self = this; ++ ++ this.partner_search_widget.reset_filter(); ++ }, ++ ++ close: function(){ ++ this._super(); ++ this.pos_widget.order_widget.set_numpad_state(null); ++ this.pos_widget.payment_screen.set_numpad_state(null); ++ }, ++ ++ }); ++ + module.ProductScreenWidget = module.ScreenWidget.extend({ + template:'ProductScreenWidget', + + scale_screen: 'scale_invite', ++ back_screen: 'partner', + client_next_screen: 'client_payment', + + show_numpad: true, +@@ -775,6 +819,16 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa + } + }); + } ++ ++ this.back_button = this.add_action_button({ ++ label: 'Back', ++ icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png', ++ click: function(){ ++ self.pos.get('selectedOrder').get('orderLines').reset([]); ++ self.pos_widget.numpad.state.trigger('set_value','remove'); ++ self.pos_widget.screen_selector.set_current_screen(self.back_screen); ++ }, ++ }); + }, + + close: function(){ +@@ -846,6 +900,63 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa + }, + }); + ++ module.PartnerCreateScreenWidget = module.ScreenWidget.extend({ ++ template: 'PartnerCreateScreenWidget', ++ show_numpad: false, ++ show_leftpane: false, ++ back_screen: 'partner', ++ next_screen: 'products', ++ init: function(parent, options) { ++ this._super(parent,options); ++ this.model = options.model; ++ }, ++ show: function(){ ++ this._super(); ++ var self = this; ++ ++ this.back_button = this.add_action_button({ ++ label: 'Back', ++ icon: '/point_of_sale/static/src/img/icons/png48/go-previous.png', ++ click: function(){ ++ self.pos_widget.screen_selector.set_current_screen(self.back_screen); ++ }, ++ }); ++ this.validate_button = this.add_action_button({ ++ label: 'Validate', ++ name: 'validation', ++ icon: '/point_of_sale/static/src/img/icons/png48/validate.png', ++ click: function(){ ++ self.validatePartner(); ++ }, ++ }); ++ this.updatePartnerCreateSummary(); ++ }, ++ close: function(){ ++ this._super(); ++ }, ++ back: function() { ++ this.pos_widget.screen_selector.set_current_screen(self.back_screen); ++ }, ++ validatePartner: function() { ++ var partner = this.pos.get('selectedPartner'); ++ ++ this.pos.push_partner(partner.exportAsJSON()); ++ this.pos.get('selectedOrder').addPartner({name:partner.name}); // NOTE: .id set asynchronously in .done() ++ this.pos_widget.screen_selector.set_current_screen(this.next_screen); ++ }, ++ renderElement: function() { ++ this._super(); ++ this.updatePartnerCreateSummary(); ++ }, ++ updatePartnerCreateSummary: function() { ++ var partner = this.pos.get('selectedPartner'); ++ if(partner){ ++ console.log("[PartnerCreateScreenWidget] [updatePartnerCreateSummary] partner=",partner.get('name')); ++ this.$('#partner-create-name').html(partner.get('name')); ++ } ++ }, ++ }); ++ + module.PaymentScreenWidget = module.ScreenWidget.extend({ + template: 'PaymentScreenWidget', + back_screen: 'products', +diff --git a/static/src/js/widgets.js b/static/src/js/widgets.js +index 874c387..9b6c120 100644 +--- a/static/src/js/widgets.js ++++ b/static/src/js/widgets.js +@@ -282,6 +282,27 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa + }, + }); + ++ module.PartnerWidget = module.PosBaseWidget.extend({ ++ template: 'PartnerWidget', ++ init: function(parent, options) { ++ this._super(parent,options); ++ this.model = options.model; ++ this.next_screen = options.next_screen; //when a partner is clicked, this screen is set ++ this.click_partner_action = options.click_partner_action; ++ }, ++ // returns the url of the partner thumbnail ++ renderElement: function() { ++ this._super(); ++ this.$('img').replaceWith(this.pos_widget.image_cache.get_image(this.model.get_image_url())); ++ var self = this; ++ $("a", this.$el).click(function(e){ ++ if(self.click_partner_action){ ++ self.click_partner_action(self.model); ++ } ++ }); ++ }, ++ }); ++ + module.ProductWidget = module.PosBaseWidget.extend({ + template: 'ProductWidget', + init: function(parent, options) { +@@ -289,6 +310,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa + this.model = options.model; + this.model.attributes.weight = options.weight; + this.next_screen = options.next_screen; //when a product is clicked, this screen is set ++ //this.back_screen = options.back_screen; + this.click_product_action = options.click_product_action; + }, + // returns the url of the product thumbnail +@@ -465,6 +487,79 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa + + module.CategoryButton = module.PosBaseWidget.extend({ + }); ++ ++ module.PartnerSearchWidget = module.PosBaseWidget.extend({ ++ template: 'PartnerSearchWidget', ++ init: function(parent, options){ ++ var self = this; ++ this._super(parent,options); ++ }, ++ ++ get_image_url: function(partner){ ++ return instance.session.url('/web/binary/image', {model: 'res.partner', field: 'image', id: partner.id}); ++ }, ++ ++ renderElement: function(){ ++ var self = this; ++ this._super(); ++ this.filter(); ++ }, ++ reset_filter: function(){ ++ this.renderElement(); ++ this.filter(); ++ }, ++ filter: function(category){ ++ var self = this; ++ ++ var partners = self.pos.db.get_partner_list(); ++ self.pos.get('partners').reset(partners); ++ ++ // filter the partners according to the search string ++ this.$('.searchbox input').keyup(function(){ ++ query = $(this).val(); ++ if(query){ ++ console.log("[PartnerSearchWidget] [filter]: query:", query); ++ var partners = self.pos.db.search_partner(query); ++ self.pos.get('partners').reset(partners); ++ //if(partners.length == 0) { ++ console.log("[PartnerSearchWidget] [filter]: no result"); ++ self.query = query; ++ self.$('.search-create').fadeIn(); ++ //}else{ ++ // self.$('.search-create').fadeOut(); ++ //} ++ self.$('.search-clear').fadeIn(); ++ }else{ ++ var partners = self.pos.db.get_partner_list(); ++ self.pos.get('partners').reset(partners); ++ self.$('.search-clear').fadeOut(); ++ self.$('.search-create').fadeOut(); ++ } ++ }); ++ ++ //this.$('.searchbox input').click(function(){}); //Why ??? ++ ++ //reset the search when clicking on reset ++ this.$('.search-clear').click(function(){ ++ var partners = self.pos.db.get_partner_list(); ++ self.pos.get('partners').reset(partners); ++ self.$('.searchbox input').val('').focus(); ++ self.$('.search-clear').fadeOut(); ++ self.$('.search-create').fadeOut(); ++ }); ++ this.$('.search-create').click(function(){ ++ var partners = self.pos.db.get_partner_list(); ++ self.pos.get('partners').reset(partners); ++ self.$('.searchbox input').val('').focus(); ++ self.$('.search-clear').fadeOut(); ++ self.$('.search-create').fadeOut(); ++ console.log("[PartnerSearchWidget] [filter] [search-create] query=",self.query); ++ self.pos.add_new_partner({name:self.query}); ++ self.pos_widget.screen_selector.set_current_screen('partner_create'); ++ }); ++ }, ++ }); ++ + module.ProductCategoriesWidget = module.PosBaseWidget.extend({ + template: 'ProductCategoriesWidget', + init: function(parent, options){ +@@ -588,7 +683,61 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa + }); + }, + }); ++ ++ module.PartnerListWidget = module.ScreenWidget.extend({ ++ template:'PartnerListWidget', ++ init: function(parent, options) { ++ var self = this; ++ this._super(parent,options); ++ this.model = options.model; ++ this.partnerwidgets = []; ++ //this.weight = options.weight || 0; ++ this.show_scale = options.show_scale || false; ++ this.next_screen = options.next_screen || false; ++ this.click_partner_action = options.click_partner_action; + ++ this.pos.get('partners').bind('reset', function(){ ++ self.renderElement(); ++ }); ++ }, ++ renderElement: function() { ++ var self = this; ++ this._super(); ++ ++ // free subwidgets memory from previous renders ++ for(var i = 0, len = this.partnerwidgets.length; i < len; i++){ ++ this.partnerwidgets[i].destroy(); ++ } ++ this.partnerwidgets = []; ++ if(this.scrollbar){ ++ this.scrollbar.destroy(); ++ } ++ var partners = this.pos.get('partners').models || []; ++ for(var i = 0, len = partners.length; i < len; i++){ ++ //console.log("[PartnerListWidget] [renderElement]: partners[i].get('name')=",partners[i].get('name')); ++ var partner = new module.PartnerWidget(self, { ++ model: partners[i], ++ click_partner_action: this.click_partner_action, ++ }); ++ this.partnerwidgets.push(partner); ++ partner.appendTo(this.$('.partner-list')); ++ } ++ this.scrollbar = new module.ScrollbarWidget(this,{ ++ target_widget: this, ++ target_selector: '.partner-list-scroller', ++ on_show: function(){ ++ self.$('.partner-list-scroller').css({'padding-right':'62px'},100); ++ }, ++ on_hide: function(){ ++ self.$('.partner-list-scroller').css({'padding-right':'0px'},100); ++ }, ++ }); ++ ++ this.scrollbar.replace(this.$('.placeholder-ScrollbarWidget')); ++ ++ }, ++ }); ++ + module.ProductListWidget = module.ScreenWidget.extend({ + template:'ProductListWidget', + init: function(parent, options) { +@@ -599,6 +748,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa + this.weight = options.weight || 0; + this.show_scale = options.show_scale || false; + this.next_screen = options.next_screen || false; ++ //this.back_screen = options.back_screen || false; + this.click_product_action = options.click_product_action; + + this.pos.get('products').bind('reset', function(){ +@@ -899,6 +1049,9 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa + + // -------- Screens --------- + ++ this.partner_screen = new module.PartnerScreenWidget(this,{}); ++ this.partner_screen.appendTo($('#rightpane')); ++ + this.product_screen = new module.ProductScreenWidget(this,{}); + this.product_screen.appendTo($('#rightpane')); + +@@ -908,6 +1061,9 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa + this.payment_screen = new module.PaymentScreenWidget(this, {}); + this.payment_screen.appendTo($('#rightpane')); + ++ this.partner_create_screen = new module.PartnerCreateScreenWidget(this, {}); ++ this.partner_create_screen.appendTo($('#rightpane')); ++ + this.welcome_screen = new module.WelcomeScreenWidget(this,{}); + this.welcome_screen.appendTo($('#rightpane')); + +@@ -993,6 +1149,8 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa + 'scale': this.scale_screen, + 'receipt' : this.receipt_screen, + 'welcome' : this.welcome_screen, ++ 'partner' : this.partner_screen, ++ 'partner_create' : this.partner_create_screen, + }, + popup_set:{ + 'help': this.help_popup, +@@ -1003,7 +1161,7 @@ function openerp_pos_widgets(instance, module){ //module is instance.point_of_sa + 'choose-receipt': this.choose_receipt_popup, + }, + default_client_screen: 'welcome', +- default_cashier_screen: 'products', ++ default_cashier_screen: 'partner', + default_mode: this.pos.iface_self_checkout ? 'client' : 'cashier', + }); + +diff --git a/static/src/xml/pos.xml b/static/src/xml/pos.xml +index 3da76ec..6aabe35 100644 +--- a/static/src/xml/pos.xml ++++ b/static/src/xml/pos.xml +@@ -185,6 +185,49 @@ + + + ++ ++
++ ++
++
++ ++ ++
++
++
    ++
++
++
++ ++
++
++ ++ ++
++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++
++ ++
++
++ ++
++
++
++
++ + +
+

Product Weighting

+@@ -210,6 +253,41 @@ +
+
+ ++ ++
++

Partner Create

++
++
++
++
++ ++ Name: ++ ++ ++ ++
++ ++ ++
++
++
++
++ + +
+

Payment

+@@ -370,6 +448,30 @@ +
+
+ ++ ++
  • ++ ++
    ++ ++ ++
    ++
    ++ ++ ++
    ++
    ++
  • ++
    ++ + +
  • + -- 2.20.1