From 30e4d89a62c21e76db651ca54f8c132b96e7668d Mon Sep 17 00:00:00 2001 From: David Arnold Date: Tue, 14 Aug 2018 16:17:33 +0000 Subject: [PATCH] [IMP] point_of_sale: allow to choose loaded categs Before this commit, it was not possible to effectively limit the available products when a company had several POS with different product offers. This commit allows limiting the available product categories for a single POS. Now, a company can serve different POS with different product offerings. Anecdotal Note: This was especially annoying as the POS only shows 100 products. If those happened to be the products not served by a signle POS, the only work around was using the search bar (on a tablet?!?) closes odoo/odoo#28393 --- addons/point_of_sale/models/pos_config.py | 19 ++++++++++++++ addons/point_of_sale/static/src/js/db.js | 25 ++++++++++--------- addons/point_of_sale/static/src/js/models.js | 23 +++++++++++------ .../point_of_sale/views/pos_config_view.xml | 17 ++++++++++++- 4 files changed, 64 insertions(+), 20 deletions(-) diff --git a/addons/point_of_sale/models/pos_config.py b/addons/point_of_sale/models/pos_config.py index 65b43171f43..00608a966d3 100644 --- a/addons/point_of_sale/models/pos_config.py +++ b/addons/point_of_sale/models/pos_config.py @@ -63,6 +63,13 @@ class PosConfig(models.Model): def _compute_default_customer_html(self): return self.env['ir.qweb'].render('point_of_sale.customer_facing_display_html') + @api.depends('limit_categories', 'iface_available_categ_ids') + def _compute_iface_start_categ_domain_ids(self): + if self.limit_categories: + self.iface_start_categ_domain_ids = self.iface_available_categ_ids + else: + self.iface_start_categ_domain_ids = self.env['pos.category'].search([]) + name = fields.Char(string='Point of Sale Name', index=True, required=True, help="An internal identification of the point of sale.") is_installed_account_accountant = fields.Boolean(string="Is the Full Accounting Installed", compute="_compute_is_installed_account_accountant") @@ -103,6 +110,10 @@ class PosConfig(models.Model): iface_tax_included = fields.Selection([('subtotal', 'Tax-Excluded Price'), ('total', 'Tax-Included Price')], string="Tax Display", default='subtotal', required=True) iface_start_categ_id = fields.Many2one('pos.category', string='Initial Category', help='The point of sale will display this product category by default. If no category is specified, all available products will be shown.') + iface_available_categ_ids = fields.Many2many('pos.category', string='Available Category Tree', + help='The point of sale will only display products which are within one of the selected category trees. If no category is specified, all available products will be shown') + iface_start_categ_domain_ids = fields.Many2many('pos.category', compute='_compute_iface_start_categ_domain_ids', store=False, + help='Technical field used as a domain for iface_start_categ_id.') iface_display_categ_images = fields.Boolean(string='Display Category Pictures', help="The product categories will be displayed with pictures.") restrict_price_control = fields.Boolean(string='Restrict Price Modifications to Managers', @@ -154,6 +165,7 @@ class PosConfig(models.Model): tax_regime = fields.Boolean("Tax Regime") tax_regime_selection = fields.Boolean("Tax Regime Selection value") start_category = fields.Boolean("Set Start Category") + limit_categories = fields.Boolean("Restrict Available Product Categories") module_account = fields.Boolean(string='Invoicing', help='Enables invoice generation from the Point of Sale.') module_pos_restaurant = fields.Boolean("Is a Bar/Restaurant") module_pos_discount = fields.Boolean("Global Discounts") @@ -307,6 +319,13 @@ class PosConfig(models.Model): if not self.start_category: self.iface_start_categ_id = False + @api.onchange('limit_categories', 'iface_available_categ_ids', 'iface_start_categ_id') + def _onchange_limit_categories(self): + if not self.limit_categories: + self.iface_available_categ_ids = False + elif self.iface_start_categ_id not in self.iface_available_categ_ids: + self.iface_start_categ_id = False + @api.onchange('is_header_or_footer') def _onchange_header_footer(self): if not self.is_header_or_footer: diff --git a/addons/point_of_sale/static/src/js/db.js b/addons/point_of_sale/static/src/js/db.js index 5d544f6b53d..ed08d62a17f 100644 --- a/addons/point_of_sale/static/src/js/db.js +++ b/addons/point_of_sale/static/src/js/db.js @@ -94,19 +94,20 @@ var PosDB = core.Class.extend({ name : 'Root', }; } - for(var i=0, len = categories.length; i < len; i++){ - this.category_by_id[categories[i].id] = categories[i]; - } - len = categories.length; - for(i=0; i < len; i++){ - var cat = categories[i]; - var parent_id = cat.parent_id[0] || this.root_category_id; - this.category_parent[cat.id] = cat.parent_id[0]; - if(!this.category_childs[parent_id]){ - this.category_childs[parent_id] = []; + categories.forEach(function(cat){ + self.category_by_id[cat.id] = cat; + }); + categories.forEach(function(cat){ + var parent_id = cat.parent_id[0]; + if(!(parent_id && self.category_by_id[parent_id])){ + parent_id = self.root_category_id; } - this.category_childs[parent_id].push(cat.id); - } + self.category_parent[cat.id] = parent_id; + if(!self.category_childs[parent_id]){ + self.category_childs[parent_id] = []; + } + self.category_childs[parent_id].push(cat.id); + }); function make_ancestors(cat_id, ancestors){ self.category_ancestors[cat_id] = ancestors; diff --git a/addons/point_of_sale/static/src/js/models.js b/addons/point_of_sale/static/src/js/models.js index 68e819f30b7..8f82fa043da 100644 --- a/addons/point_of_sale/static/src/js/models.js +++ b/addons/point_of_sale/static/src/js/models.js @@ -355,7 +355,9 @@ exports.PosModel = Backbone.Model.extend({ },{ model: 'pos.category', fields: ['id', 'name', 'parent_id', 'child_id'], - domain: null, + domain: function(self) { + return self.config.limit_categories ? [['id', 'in', self.config.iface_available_categ_ids]] : []; + }, loaded: function(self, categories){ self.db.add_categories(categories); }, @@ -365,7 +367,13 @@ exports.PosModel = Backbone.Model.extend({ 'barcode', 'default_code', 'to_weight', 'uom_id', 'description_sale', 'description', 'product_tmpl_id','tracking'], order: _.map(['sequence','default_code','name'], function (name) { return {name: name}; }), - domain: [['sale_ok','=',true],['available_in_pos','=',true]], + domain: function(self){ + var domain = [['sale_ok','=',true],['available_in_pos','=',true]]; + if (self.config.limit_categories) { + domain.push(['pos_categ_id', 'in', self.config.iface_available_categ_ids]); + } + return domain; + }, context: function(self){ return { display_default_code: false }; }, loaded: function(self, products){ var using_company_currency = self.config.currency_id[0] === self.company.currency_id[0]; @@ -1326,7 +1334,12 @@ exports.Orderline = Backbone.Model.extend({ this.pos = options.pos; this.order = options.order; if (options.json) { - this.init_from_JSON(options.json); + try { + this.init_from_JSON(options.json); + } catch(error) { + console.error('ERROR: attempting to recover product ID', json.product_id, + 'not available in the point of sale. Correct the product or clean the browser cache.'); + } return; } this.product = options.product; @@ -1347,10 +1360,6 @@ exports.Orderline = Backbone.Model.extend({ }, init_from_JSON: function(json) { this.product = this.pos.db.get_product_by_id(json.product_id); - if (!this.product) { - console.error('ERROR: attempting to recover product ID', json.product_id, - 'not available in the point of sale. Correct the product or clean the browser cache.'); - } this.set_product_lot(this.product); this.price = json.price_unit; this.set_discount(json.discount); diff --git a/addons/point_of_sale/views/pos_config_view.xml b/addons/point_of_sale/views/pos_config_view.xml index 5b8dd68a532..f549fbc2d2f 100644 --- a/addons/point_of_sale/views/pos_config_view.xml +++ b/addons/point_of_sale/views/pos_config_view.xml @@ -73,6 +73,7 @@
+
@@ -82,7 +83,21 @@ Start selling from a default product category
- + +
+ + +
+
+ +
+
+