From 5377a9e2d661a400cc13701492c64a395b36016f Mon Sep 17 00:00:00 2001 From: Frederic van der Essen Date: Wed, 17 Sep 2014 16:01:53 +0200 Subject: [PATCH] [IMP] point_of_sale: partner edition ! -> create partners, edit partners, assign them barcodes, take pictures, etc. --- addons/point_of_sale/point_of_sale.py | 23 ++ addons/point_of_sale/static/src/css/pos.css | 64 +++++- addons/point_of_sale/static/src/js/db.js | 12 +- addons/point_of_sale/static/src/js/models.js | 16 +- addons/point_of_sale/static/src/js/screens.js | 213 ++++++++++++++++-- addons/point_of_sale/static/src/xml/pos.xml | 69 ++++-- 6 files changed, 348 insertions(+), 49 deletions(-) diff --git a/addons/point_of_sale/point_of_sale.py b/addons/point_of_sale/point_of_sale.py index aeb1ca8ea46..28b3795b0e0 100644 --- a/addons/point_of_sale/point_of_sale.py +++ b/addons/point_of_sale/point_of_sale.py @@ -1403,4 +1403,27 @@ class product_template(osv.osv): 'available_in_pos': True, } +class res_partner(osv.osv): + _inherit = 'res.partner' + + def create_from_ui(self, cr, uid, partner, context=None): + """ create or modify a partner from the point of sale ui. + partner contains the partner's fields. """ + + #image is a dataurl, get the data after the comma + if partner.get('image',False): + img = partner['image'].split(',')[1] + partner['image'] = img + + if partner.get('id',False): # Modifying existing partner + partner_id = partner['id'] + del partner['id'] + self.write(cr, uid, [partner_id], partner, context=context) + else: + partner_id = self.create(cr, uid, partner, context=context) + + return partner_id + + + # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: diff --git a/addons/point_of_sale/static/src/css/pos.css b/addons/point_of_sale/static/src/css/pos.css index 8cbf8700a83..d035c79adcc 100644 --- a/addons/point_of_sale/static/src/css/pos.css +++ b/addons/point_of_sale/static/src/css/pos.css @@ -6,14 +6,18 @@ } /* --- Styling of OpenERP Elements --- */ -.ui-dialog{ +.ui-dialog, .modal-dialog { background: white; padding: 10px; border-radius: 3px; font-family: sans-serif; box-shadow: 0px 10px 40px rgba(0,0,0,0.4); + position: absolute; + top: 30px; + height: 400px; + overflow: scroll; } -.ui-dialog button{ +.ui-dialog button, .modal-dialog button { padding: 8px; min-width: 48px; } @@ -1256,16 +1260,48 @@ td { float: left; margin-right: 16px; background: white; + position: relative; } -.pos .clientlist-screen .client-picture > img{ - vertical-align: middle; +.pos .clientlist-screen .client-picture > img { + position: absolute; + top: -9999px; + bottom: -9999px; + right: -9999px; + left: -9999px; max-height: 64px; + margin: auto; } -.pos .clientlist-screen .client-name{ +.pos .clientlist-screen .client-picture > .fa { + line-height: 64px; + font-size: 32px; +} +.pos .clientlist-screen .client-picture .image-uploader { + position: absolute; + z-index: 1000; + top: 0; + left: 0; + right: 0; + bottom: 0; + opacity: 0; + cursor: pointer; +} +.pos .clientlist-screen .client-name { font-size: 32px; line-height: 64px; margin-bottom:16px; } +.pos .clientlist-screen .edit-buttons { + position: absolute; + right: 16px; + top: 10px; +} +.pos .clientlist-screen .edit-buttons .button{ + display: inline-block; + margin-left: 16px; + color: rgb(128,128,128); + cursor: pointer; + font-size: 36px; +} .pos .clientlist-screen .client-details-box{ position: relative; font-size: 16px; @@ -1284,21 +1320,29 @@ td { .pos .clientlist-screen .client-detail > .label{ font-weight: bold; display: inline-block; - width: 64px; + width: 75px; text-align: right; margin-right: 8px; } -.pos .clientlist-screen .client-details input { +.pos .clientlist-screen .client-details input, +.pos .clientlist-screen .client-details select +{ padding: 4px; border-radius: 3px; border: solid 1px #cecbcb; + margin-bottom: 4px; + background: white; + font-family: "Lato","Lucida Grande", Helvetica, Verdana, Arial; + color: #555555; width: 340px; + font-size: 14px; + box-sizing: border-box; } .pos .clientlist-screen .client-details input.client-name { font-size: 24px; line-height: 24px; margin: 18px 6px; - width: 330px; + width: 340px; } .pos .clientlist-screen .client-detail > .empty{ opacity: 0.3; @@ -1312,6 +1356,10 @@ td { .pos .clientlist-screen .searchbox input{ width: 120px; } +.pos .clientlist-screen .button.new-customer { + left: 50%; + margin-left: 120px; +} /* ********* The OrderWidget ********* */ diff --git a/addons/point_of_sale/static/src/js/db.js b/addons/point_of_sale/static/src/js/db.js index 7a9115298c0..086bac948f3 100644 --- a/addons/point_of_sale/static/src/js/db.js +++ b/addons/point_of_sale/static/src/js/db.js @@ -220,12 +220,12 @@ function openerp_pos_db(instance, module){ }, add_partners: function(partners){ var updated_count = 0; + var new_write_date = ''; for(var i = 0, len = partners.length; i < len; i++){ var partner = partners[i]; - if (!this.partner_write_date) { - this.partner_write_date = partner.write_date; - } else if ( this.partner_by_id[partner.id] && + if ( this.partner_write_date && + this.partner_by_id[partner.id] && new Date(this.partner_write_date).getTime() + 1000 >= new Date(partner.write_date).getTime() ) { // FIXME: The write_date is stored with milisec precision in the database @@ -233,8 +233,8 @@ function openerp_pos_db(instance, module){ // you read partners modified strictly after time X, you get back partners that were // modified X - 1 sec ago. continue; - } else if ( this.partner_write_date < partner.write_date ) { - this.partner_write_date = partner.write_date; + } else if ( new_write_date < partner.write_date ) { + new_write_date = partner.write_date; } if (!this.partner_by_id[partner.id]) { this.partner_sorted.push(partner.id); @@ -244,6 +244,8 @@ function openerp_pos_db(instance, module){ updated_count += 1; } + this.partner_write_date = new_write_date || this.partner_write_date; + if (updated_count) { // If there were updates, we need to completely // rebuild the search string and the ean13 indexing diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 199d7810f48..b6ac5e8b81a 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -135,7 +135,7 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal loaded: function(self,users){ self.user = users[0]; }, },{ model: 'res.company', - fields: [ 'currency_id', 'email', 'website', 'company_registry', 'vat', 'name', 'phone', 'partner_id' ], + fields: [ 'currency_id', 'email', 'website', 'company_registry', 'vat', 'name', 'phone', 'partner_id' , 'country_id'], domain: function(self){ return [['id','=',self.user.company_id[0]]]; }, loaded: function(self,companies){ self.company = companies[0]; }, },{ @@ -159,12 +159,24 @@ function openerp_pos_models(instance, module){ //module is instance.point_of_sal loaded: function(self,users){ self.users = users; }, },{ model: 'res.partner', - fields: ['name','street','city','country_id','phone','zip','mobile','email','ean13','write_date'], + fields: ['name','street','city','state_id','country_id','vat','phone','zip','mobile','email','ean13','write_date'], domain: null, loaded: function(self,partners){ self.partners = partners; self.db.add_partners(partners); }, + },{ + model: 'res.country', + fields: ['name'], + loaded: function(self,countries){ + self.countries = countries; + self.company.country = null; + for (var i = 0; i < countries.length; i++) { + if (countries[i].id === self.company.country_id[0]){ + self.company.country = countries[i]; + } + } + }, },{ model: 'account.tax', fields: ['name','amount', 'price_include', 'type'], diff --git a/addons/point_of_sale/static/src/js/screens.js b/addons/point_of_sale/static/src/js/screens.js index 807fb409733..3ca2a81e45b 100644 --- a/addons/point_of_sale/static/src/js/screens.js +++ b/addons/point_of_sale/static/src/js/screens.js @@ -573,13 +573,16 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa self.pos_widget.screen_selector.back(); }); + this.$('.new-customer').click(function(){ + self.display_client_details('edit',{ + 'country_id': self.pos.company.country_id, + }); + }); + var partners = this.pos.db.get_partners_sorted(1000); this.render_list(partners); - this.pos.load_new_partners().then(function(){ - // will only get called if new partners were reloaded. - self.render_list(self.pos.db.get_partners_sorted(1000)); - }); + this.reload_partners(); if( this.old_client ){ this.display_client_details('show',this.old_client,0); @@ -609,6 +612,13 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa self.clear_search(); }); }, + barcode_client_action: function(code){ + if (this.editing_client) { + this.$('.detail.barcode').val(code.code); + } else if (this.pos.db.get_partner_by_ean13(code.code)) { + this.display_client_details('show',this.pos.db.get_partner_by_ean13(code.code)); + } + }, perform_search: function(query, associate_result){ if(query){ var customers = this.pos.db.search_partner(query); @@ -663,7 +673,10 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa }, toggle_save_button: function(){ var $button = this.$('.button.next'); - if( this.new_client ){ + if (this.editing_client) { + $button.addClass('oe_hidden'); + return; + } else if( this.new_client ){ if( !this.old_client){ $button.text(_t('Set Customer')); }else{ @@ -695,43 +708,207 @@ function openerp_pos_screens(instance, module){ //module is instance.point_of_sa partner_icon_url: function(id){ return '/web/binary/image?model=res.partner&id='+id+'&field=image_small'; }, + + // ui handle for the 'edit selected customer' action + edit_client_details: function(partner) { + this.display_client_details('edit',partner); + }, + + // ui handle for the 'cancel customer edit changes' action + undo_client_details: function(partner) { + if (!partner.id) { + this.display_client_details('hide'); + } else { + this.display_client_details('show',partner); + } + }, + + // what happens when we save the changes on the client edit form -> we fetch the fields, sanitize them, + // send them to the backend for update, and call saved_client_details() when the server tells us the + // save was successfull. + save_client_details: function(partner) { + var self = this; + + var fields = {} + this.$('.client-details-contents .detail').each(function(idx,el){ + fields[el.name] = el.value; + }); + + if (!fields.name) { + this.pos_widget.screen_selector.show_popup('error',{ + message: _t('A Customer Name Is Required'), + }); + return; + } + + if (this.uploaded_picture) { + fields.image = this.uploaded_picture; + } + + fields.id = partner.id || false; + fields.country_id = fields.country_id || false; + fields.ean13 = fields.ean13 ? this.pos.barcode_reader.sanitize_ean(fields.ean13) : false; + + new instance.web.Model('res.partner').call('create_from_ui',[fields]).then(function(partner_id){ + self.saved_client_details(partner_id); + }); + }, + + // what happens when we've just pushed modifications for a partner of id partner_id + saved_client_details: function(partner_id){ + var self = this; + this.reload_partners().then(function(){ + var partner = self.pos.db.get_partner_by_id(partner_id); + if (partner) { + self.new_client = partner; + self.toggle_save_button(); + self.display_client_details('show',partner); + } else { + // should never happen, because create_from_ui must return the id of the partner it + // has created, and reload_partner() must have loaded the newly created partner. + self.display_client_details('hide'); + } + }); + }, + + // resizes an image, keeping the aspect ratio intact, + // the resize is useful to avoid sending 12Mpixels jpegs + // over a wireless connection. + resize_image_to_dataurl: function(img, maxwidth, maxheight, callback){ + img.onload = function(){ + var png = new Image(); + var canvas = document.createElement('canvas'); + var ctx = canvas.getContext('2d'); + var ratio = 1; + + if (img.width > maxwidth) { + ratio = maxwidth / img.width; + } + if (img.height * ratio > maxheight) { + ratio = maxheight / img.height; + } + var width = Math.floor(img.width * ratio); + var height = Math.floor(img.height * ratio); + + canvas.width = width; + canvas.height = height; + ctx.drawImage(img,0,0,width,height); + + var dataurl = canvas.toDataURL(); + callback(dataurl); + } + }, + + // Loads and resizes a File that contains an image. + // callback gets a dataurl in case of success. + load_image_file: function(file, callback){ + var self = this; + if (!file.type.match(/image.*/)) { + this.pos_widget.screen_selector.show_popup('error',{ + message:_t('Unsupported File Format'), + comment:_t('Only web-compatible Image formats such as .png or .jpeg are supported'), + }); + return; + } + + var reader = new FileReader(); + reader.onload = function(event){ + var dataurl = event.target.result; + var img = new Image(); + img.src = dataurl; + self.resize_image_to_dataurl(img,800,600,callback); + } + reader.onerror = function(){ + self.pos_widget.screen_selector.show_popup('error',{ + message:_t('Could Not Read Image'), + comment:_t('The provided file could not be read due to an unknown error'), + }); + }; + reader.readAsDataURL(file); + }, + + // This fetches partner changes on the server, and in case of changes, + // rerenders the affected views + reload_partners: function(){ + var self = this; + return this.pos.load_new_partners().then(function(){ + self.render_list(self.pos.db.get_partners_sorted(1000)); + + // update the currently assigned client if it has been changed in db. + var curr_client = self.pos.get_order().get_client(); + if (curr_client) { + self.pos.get_order().set_client(self.pos.db.get_partner_by_id(curr_client.id)); + } + }); + }, + + // Shows,hides or edit the customer details box : + // visibility: 'show', 'hide' or 'edit' + // partner: the partner object to show or edit + // clickpos: the height of the click on the list (in pixel), used + // to maintain consistent scroll. display_client_details: function(visibility,partner,clickpos){ + var self = this; + var contents = this.$('.client-details-contents'); + var parent = this.$('.client-list').parent(); + var scroll = parent.scrollTop(); + var height = contents.height(); + + contents.off('click','.button.edit'); + contents.off('click','.button.save'); + contents.off('click','.button.undo'); + contents.on('click','.button.edit',function(){ self.edit_client_details(partner); }); + contents.on('click','.button.save',function(){ self.save_client_details(partner); }); + contents.on('click','.button.undo',function(){ self.undo_client_details(partner); }); + this.editing_client = false; + this.uploaded_picture = null; + if(visibility === 'show'){ - var contents = this.$('.client-details-contents'); - var parent = this.$('.client-list').parent(); - var old_scroll = parent.scrollTop(); - var old_height = contents.height(); contents.empty(); - contents.append($(QWeb.render('ClientDetailsEdit',{widget:this,partner:partner}))); + contents.append($(QWeb.render('ClientDetails',{widget:this,partner:partner}))); + var new_height = contents.height(); if(!this.details_visible){ - if(clickpos < old_scroll + new_height + 20 ){ + if(clickpos < scroll + new_height + 20 ){ parent.scrollTop( clickpos - 20 ); }else{ parent.scrollTop(parent.scrollTop() + new_height); } }else{ - parent.scrollTop(parent.scrollTop() - old_height + new_height); + parent.scrollTop(parent.scrollTop() - height + new_height); } this.details_visible = true; - }else if(visibility === 'hide'){ - var contents = this.$('.client-details-contents'); - var parent = this.$('.client-list').parent(); - var scroll = parent.scrollTop(); - var height = contents.height(); + this.toggle_save_button(); + } else if (visibility === 'edit') { + this.editing_client = true; + contents.empty(); + contents.append($(QWeb.render('ClientDetailsEdit',{widget:this,partner:partner}))); + this.toggle_save_button(); + + contents.find('.image-uploader').on('change',function(){ + self.load_image_file(event.target.files[0],function(res){ + if (res) { + contents.find('.client-picture img, .client-picture .fa').remove(); + contents.find('.client-picture').append(""); + contents.find('.detail.picture').remove(); + self.uploaded_picture = res; + } + }); + }); + } else if (visibility === 'hide') { contents.empty(); if( height > scroll ){ contents.css({height:height+'px'}); contents.animate({height:0},400,function(){ contents.css({height:''}); }); - //parent.scrollTop(0); }else{ parent.scrollTop( parent.scrollTop() - height); } this.details_visible = false; + this.toggle_save_button(); } }, close: function(){ diff --git a/addons/point_of_sale/static/src/xml/pos.xml b/addons/point_of_sale/static/src/xml/pos.xml index 58d20a82e2a..38a002774ab 100644 --- a/addons/point_of_sale/static/src/xml/pos.xml +++ b/addons/point_of_sale/static/src/xml/pos.xml @@ -297,40 +297,61 @@
- + + + + + + + +
+ +
+
+
-
Street - +
City - +
- ZIP - + Postcode +
Country - +
- email - + Email +
- phone - + Phone +
- ID - + Barcode + +
+
+ Tax ID +
@@ -342,6 +363,9 @@
+
+
+
@@ -349,11 +373,11 @@
- email + Email
- phone + Phone @@ -364,7 +388,7 @@
- ID + Barcode @@ -372,6 +396,15 @@ N/A
+
+ Tax ID + + + + + N/A + +
@@ -390,6 +423,10 @@ + + + + Select Customer