--- /dev/null
+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 @@
+ </div>
+ </t>
+
++ <t t-name="PartnerSearchWidget">
++ <header>
++ <div class="searchbox">
++ <input placeholder="Search Products" />
++ <img class="search-create" src="/point_of_sale/static/src/img/validate-icon.png" />
++ <img class="search-clear" src="/point_of_sale/static/src/img/search_reset.gif" />
++ </div>
++ </header>
++ </t>
++
++ <t t-name="PartnerListWidget">
++ <div class='partner-list-container'>
++ <div class="partner-list-scroller">
++ <ol id="partners-screen-ol" class="partner-list">
++ </ol>
++ </div>
++ <div class="shadow-top"></div>
++ <span class="placeholder-ScrollbarWidget" />
++ </div>
++ </t>
++
++ <t t-name="PartnerScreenWidget">
++ <div id="partners-screen" class="screen">
++ <table class="layout-table">
++
++ <tr class="header-row">
++ <td class="header-cell">
++ <span class="placeholder-PartnerSearchWidget" />
++ </td>
++ </tr>
++
++ <tr class="content-row">
++ <td class="content-cell">
++ <div class="content-container">
++ <span class="placeholder-PartnerListWidget" />
++ </div>
++ </td>
++ </tr>
++
++ </table>
++ </div>
++ </t>
++
+ <t t-name="ScaleScreenWidget">
+ <div class="scale-screen screen">
+ <header><h2>Product Weighting</h2></header>
+@@ -210,6 +253,41 @@
+ </div>
+ </t>
+
++ <t t-name="PartnerCreateScreenWidget">
++ <div id="partner-create-screen" class="screen">
++ <header><h2>Partner Create</h2></header>
++ <div class="pos-step-container">
++ <div class="pos-partner-create-container">
++ <br />
++ <div class="header">
++ <span class="left-block">
++ Name:
++ </span>
++ <span class='right-block' id="partner-create-name"></span>
++ <!--
++ <span class="left-block">
++ Name2:
++ </span>
++ <span class='right-block'>
++ <t t-if="widget.model.get('name')"/>
++ </span>
++ -->
++ </div>
++ <!--<table id="partner-createlines">
++ </table>-->
++ <div class="footer">
++ <div class="infoline">
++ <span class='left-block'>
++ Address:
++ </span>
++ <span class='right-block' id="partner-create-address"></span>
++ </div>
++ </div>
++ </div>
++ </div>
++ </div>
++ </t>
++
+ <t t-name="PaymentScreenWidget">
+ <div id="payment-screen" class="screen">
+ <header><h2>Payment</h2></header>
+@@ -370,6 +448,30 @@
+ </div>
+ </t>
+
++ <t t-name="PartnerWidget">
++ <li class='partner'>
++ <a href="#">
++ <div class="partner-img">
++ <img src='' /> <!-- the partner thumbnail -->
++ <!--<t t-if="!widget.model.get('to_weight')">
++ <span class="price-tag">
++ <t t-esc="widget.format_currency(widget.model.get('price'))"/>
++ </span>
++ </t>
++ <t t-if="widget.model.get('to_weight')">
++ <span class="price-tag">
++ <t t-esc="widget.format_currency(widget.model.get('price'))+'/Kg'"/>
++ </span>
++ </t>-->
++ </div>
++ <div class="partner-name">
++ <t t-esc="widget.model.get('name')"/>
++ <!--(<t t-esc="widget.model.get('id')"/>)-->
++ </div>
++ </a>
++ </li>
++ </t>
++
+ <t t-name="ProductWidget">
+ <li class='product'>
+ <a href="#">