diff --git a/pos_product_template/README.rst b/pos_product_template/README.rst new file mode 100644 index 00000000..40fa6536 --- /dev/null +++ b/pos_product_template/README.rst @@ -0,0 +1,90 @@ +.. image:: https://img.shields.io/badge/license-AGPL--3-blue.png + :target: https://www.gnu.org/licenses/agpl + :alt: License: AGPL-3 + +==================== +POS Product Template +==================== + + + * In Point Of Sale Front End - Products list: + * Display only one product per template; + * Display template name instead of product name; + * Display products quantity instead of price; + * Click on template displays an extra screen to select Variant; + + * In Point Of Sale Front End - Variants list: + * Display all the products of the selected variant; + * Click on a attribute value filters products; + * Click on a product adds it to the current Order or display normal + extra screen if it is a weightable product; + + +Usage +===== + +Open the Point of Sale, search an article with variants. +You will see one article instead of all the variants. + +#. Go to ... + +.. image:: https://odoo-community.org/website/image/ir.attachment/5784_f2813bd/datas + :alt: Try me on Runbot + :target: https://runbot.odoo-community.org/runbot/pos/10.0 + + +Known issues / Roadmap +====================== + +* Templates with lot of variants are not shown. See https://github.com/OCA/pos/pull/135 + + +Bug Tracker +=========== + +Bugs are tracked on `GitHub Issues +`_. In case of trouble, please +check there if your issue has already been reported. If you spotted it first, +help us smash it by providing detailed and welcomed feedback. + +Credits +======= + +Images +------ + +* Odoo Community Association: `Icon `_. + +Contributors +------------ + +* Sylvain LE GAL (https://twitter.com/legalsylvain) +* Navarromiguel (https://github.com/navarromiguel) +* Damendieta (https://github.com/damendieta) +* Raphaël Reverdy (https://akretion.com) + + +Do not contact contributors directly about support or help with technical issues. + +Funders +------- + +The development of this module has been financially supported by: + +* Akretion + + +Maintainer +---------- + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +This module is maintained by the OCA. + +OCA, or the Odoo Community Association, is a nonprofit organization whose +mission is to support the collaborative development of Odoo features and +promote its widespread use. + +To contribute to this module, please visit https://odoo-community.org. diff --git a/pos_product_template/__manifest__.py b/pos_product_template/__manifest__.py index b9cb2b26..e3f9d300 100644 --- a/pos_product_template/__manifest__.py +++ b/pos_product_template/__manifest__.py @@ -1,61 +1,10 @@ # -*- encoding: utf-8 -*- -############################################################################## -# -# Point Of Sale - Product Template module for Odoo -# Copyright (C) 2014-Today Akretion (http://www.akretion.com) -# @author Sylvain LE GAL (https://twitter.com/legalsylvain) -# -# This program is free software: you can redistribute it and/or modify -# it under the terms of the GNU Affero General Public License as -# published by the Free Software Foundation, either version 3 of the -# License, or (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# You should have received a copy of the GNU Affero General Public License -# along with this program. If not, see . -# -############################################################################## { - 'name': 'Point Of Sale - Product Template', - 'summary': 'Manage Product Template in Front End Point Of Sale', - 'version': '8.0.0.2.0', + 'name': 'POS - Product Template', + 'version': '10.0.1.0.1', 'category': 'Point Of Sale', - 'description': """ -Manage Product Template in Front End Point Of Sale -================================================== - -Functionality: --------------- - * In Point Of Sale Front End - Products list: - * Display only one product per template; - * Display template name instead of product name; - * Display products quantity instead of price; - * Click on template displays an extra screen to select Variant; - - * In Point Of Sale Front End - Variants list: - * Display all the products of the selected variant; - * Click on a attribute value filters products; - * Click on a product adds it to the current Order or display normal - extra screen if it is a weightable product; - -Technical Information: ----------------------- - * Load extra model in Point Of Sale Front End: - * product.template; - * product.attribute; - * product.attribute.value; - -Copyright, Authors and Licence: -------------------------------- - * Copyright: 2014-Today, Akretion; - * Author: - * Sylvain LE GAL (https://twitter.com/legalsylvain); - * Licence: AGPL-3 (http://www.gnu.org/licenses/);""", 'author': "Akretion,Odoo Community Association (OCA)", + 'summary': 'Manage Product Template in Front End Point Of Sale', 'website': 'http://www.akretion.com', 'license': 'AGPL-3', 'depends': [ @@ -75,5 +24,5 @@ Copyright, Authors and Licence: 'images': [ 'static/src/img/screenshots/pos_product_template.png', ], - 'installable': False, + 'installable': True, } diff --git a/pos_product_template/i18n/es.po b/pos_product_template/i18n/es.po new file mode 100644 index 00000000..0d42ef20 --- /dev/null +++ b/pos_product_template/i18n/es.po @@ -0,0 +1,39 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_product_template +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 8.0\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2014-12-18 12:01+0000\n" +"PO-Revision-Date: 2014-12-18 12:01+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: \n" + +#. module: pos_product_template +#. openerp-web +#: code:addons/pos_product_template/static/src/xml/ppt.xml:31 +#, python-format +msgid "Cancel" +msgstr "Cancelar" + +#. module: pos_product_template +#. openerp-web +#: code:addons/pos_product_template/static/src/xml/ppt.xml:29 +#, python-format +msgid "Variant Selection of" +msgstr "Seleccina una variante de" + +#. module: pos_product_template +#. openerp-web +#: code:addons/pos_product_template/static/src/xml/ppt.xml:57 +#: code:addons/pos_product_template/static/src/xml/ppt.xml:95 +#, python-format +msgid "Variants" +msgstr "Variantes" + diff --git a/pos_product_template/static/src/js/ppt.js b/pos_product_template/static/src/js/ppt.js index b5318ce0..bf90edc4 100644 --- a/pos_product_template/static/src/js/ppt.js +++ b/pos_product_template/static/src/js/ppt.js @@ -1,89 +1,86 @@ -/****************************************************************************** - Point Of Sale - Product Template module for Odoo - Copyright (C) 2014-Today Akretion (http://www.akretion.com) +/* Copyright (C) 2014-Today Akretion (https://www.akretion.com) @author Sylvain LE GAL (https://twitter.com/legalsylvain) + @author Navarromiguel (https://github.com/navarromiguel) + @author Raphaël Reverdy (https://www.akretion.com) + License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). +*/ - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU Affero General Public License as - published by the Free Software Foundation, either version 3 of the - License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU Affero General Public License for more details. - - You should have received a copy of the GNU Affero General Public License - along with this program. If not, see . -******************************************************************************/ - -openerp.pos_product_template = function (instance) { - module = instance.point_of_sale; - var QWeb = instance.web.qweb; - var _t = instance.web._t; - -/* ******************************************************** -Overload: point_of_sale.ProductListWidget - -- The overload will: - - display only product template; - - Add an extra behaviour on click on a template, if template has many - variant, displaying an extra scren to select the variant; -*********************************************************** */ - module.ProductListWidget = module.ProductListWidget.extend({ - - init: function(parent, options) { - this._super(parent,options); - var self = this; - // OVERWRITE 'click_product_handler' function to do - // a different behaviour if template with one or many variants - // are selected. - this.click_product_handler = function(event){ - var product = self.pos.db.get_product_by_id(this.dataset['productId']); - - if (product.product_variant_count == 1) { - // Normal behaviour, The template has only one variant - options.click_product_action(product); - } - else{ - // Display for selection all the variants of a template - self.pos.pos_widget.screen_selector.show_popup('select-variant-popup', product.product_tmpl_id); - } - }; - }, - - /* ************************************************ - Overload: 'set_product_list' - - 'set_product_list' is a function called before displaying Products. - (at the beginning, after a category selection, after a research, etc. - we just splice all products that are not the 'primary variant' - */ - set_product_list: function(product_list){ - for (var i = product_list.length - 1; i >= 0; i--){ - if (!product_list[i].is_primary_variant){ - product_list.splice(i, 1); - } +odoo.define("pos_product_template.pos_product_template", function(require){ + "use strict"; + + var screens = require("point_of_sale.screens"); + var popups = require("point_of_sale.popups"); + var models = require('point_of_sale.models'); + var chrome = require('point_of_sale.chrome'); + var gui = require('point_of_sale.gui'); + var PosDB = require("point_of_sale.DB"); + var PosBaseWidget = require('point_of_sale.BaseWidget'); + + var core = require('web.core'); + var utils = require('web.utils'); + + var QWeb = core.qweb; + var _t = core._t; + + /* ******************************************************** + Overload: point_of_sale.ProductListWidget + + - The overload will: + - display only product template; + - Add an extra behaviour on click on a template, if template has many + variant, displaying an extra scren to select the variant; + *********************************************************** */ + var _render_product_ = screens.ProductListWidget.prototype.render_product; + + screens.ProductScreenWidget.include({ + click_product: function(product) { + if (product.product_variant_count > 1) { + this.gui.show_popup('select-variant-popup', product.product_tmpl_id); + } else { + this._super(product); } - this._super(product_list); - }, - }); - - var _render_product_ = module.ProductListWidget.prototype.render_product; - module.ProductListWidget.prototype.render_product = function(product){ - self = this; - - if (product.product_variant_count == 1){ - // Normal Display - return _render_product_.call(this, product); } - else{ + }); + screens.ProductListWidget.include({ + + set_product_list: function(product_list) { + /* ************************************************ + Overload: 'set_product_list' + + 'set_product_list' is a function called before displaying Products. + (at the beginning, after a category selection, after a research, etc. + we just remove all products that are not the 'primary variant' + */ + var list = product_list.filter(function(product) { + return product.is_primary_variant; + }); + this._super(list); + }, + + render_product: function(product){ + var self = this; + + if (product.product_variant_count == 1){ + // Normal Display + return this._super(product); + } + if (!product.is_primary_variant) { + //because screens.js:556: renderElement is called + //once before set_product_list + //So we get product.is_primary_variant + //which are not to be displayed + // + //Here we return mock element for + //products which will not be displayed + return document.createElement('div'); + } + //TODO reuse upper function var cached = this.product_cache.get_node(product.id); - if(!cached){ + if(!cached) { var image_url = this.get_product_image_url(product); - var product_html = QWeb.render('Template',{ - widget: this, - product: product, + var product_html = QWeb.render('Template',{ + widget: this, + product: product, image_url: this.get_product_image_url(product), }); var product_node = document.createElement('div'); @@ -93,45 +90,43 @@ Overload: point_of_sale.ProductListWidget return product_node; } return cached; - } - }; - - -/* ******************************************************** -Overload: point_of_sale.PosWidget - -- Add a new PopUp 'SelectVariantPopupWidget'; -*********************************************************** */ - module.PosWidget = module.PosWidget.extend({ - + }, + }); + + /* ******************************************************** + Overload: point_of_sale.PosWidget + + - Add a new PopUp 'SelectVariantPopupWidget'; + ************************************************************/ + chrome.Chrome.include({ /* Overload Section */ build_widgets: function(){ this._super(); - this.select_variant_popup = new module.SelectVariantPopupWidget(this, {}); + this.select_variant_popup = new SelectVariantPopupWidget(this, {}); this.select_variant_popup.appendTo($(this.$el)); - this.screen_selector.popup_set['select-variant-popup'] = this.select_variant_popup; + // Hide the popup because all pop up are displayed at the // beginning by default this.select_variant_popup.hide(); }, }); - -/* ******************************************************** -Define : pos_product_template.SelectVariantPopupWidget -- This widget that display a pop up to select a variant of a Template; -*********************************************************** */ - module.SelectVariantPopupWidget = module.PopUpWidget.extend({ + /* ******************************************************** + Define : pos_product_template.SelectVariantPopupWidget + + - This widget that display a pop up to select a variant of a Template; + ***********************************************************/ + var SelectVariantPopupWidget = popups.extend({ template:'SelectVariantPopupWidget', start: function(){ var self = this; // Define Variant Widget - this.variant_list_widget = new module.VariantListWidget(this,{}); + this.variant_list_widget = new VariantListWidget(this,{}); this.variant_list_widget.replace(this.$('.placeholder-VariantListWidget')); // Define Attribute Widget - this.attribute_list_widget = new module.AttributeListWidget(this,{}); + this.attribute_list_widget = new AttributeListWidget(this,{}); this.attribute_list_widget.replace(this.$('.placeholder-AttributeListWidget')); // Add behaviour on Cancel Button @@ -148,32 +143,34 @@ Define : pos_product_template.SelectVariantPopupWidget this.$('#variant-title-name').html(template.name); // Render Variants - var variant_ids = this.pos.db.template_by_id[product_tmpl_id].product_variant_ids; - var variant_list = []; - for (var i = 0, len = variant_ids.length; i < len; i++) { - variant_list.push(this.pos.db.get_product_by_id(variant_ids[i])); - } + var variant_ids = this.pos.db.template_by_id[product_tmpl_id].product_variant_ids; + var variant_list = variant_ids.map(function (variant) { + return this.pos.db.get_product_by_id(variant); + }, this); this.variant_list_widget.filters = {} this.variant_list_widget.set_variant_list(variant_list); // Render Attributes - var attribute_ids = this.pos.db.attribute_by_template_id(template.id); - var attribute_list = []; - for (var i = 0, len = attribute_ids.length; i < len; i++) { - attribute_list.push(this.pos.db.get_product_attribute_by_id(attribute_ids[i])); - } + var attribute_ids = this.pos.db.attribute_by_template_id(template.id); + var attribute_list = attribute_ids.map(function (attribute) { + return this.pos.db.get_product_attribute_by_id(attribute); + }, this); this.attribute_list_widget.set_attribute_list(attribute_list, template); - this._super(); + + if(this.$el){ + this.$el.removeClass('oe_hidden'); + } }, }); + gui.define_popup({name:'select-variant-popup', widget: SelectVariantPopupWidget}); -/* ******************************************************** -Define: pos_product_template.VariantListWidget - -- This widget will display a list of Variants; -- This widget has some part of code that come from point_of_sale.ProductListWidget; -*********************************************************** */ - module.VariantListWidget = module.PosBaseWidget.extend({ + /* ******************************************************** + Define: pos_product_template.VariantListWidget + + - This widget will display a list of Variants; + - This widget has some part of code that come from point_of_sale.ProductListWidget; + ***********************************************************/ + var VariantListWidget = PosBaseWidget.extend({ template:'VariantListWidget', init: function(parent, options) { @@ -189,7 +186,7 @@ Define: pos_product_template.VariantListWidget self.pos_widget.screen_selector.set_current_screen('scale',{product: variant}); }else{ self.__parentedParent.hide(); - self.pos.get('selectedOrder').addProduct(variant); + self.pos.get('selectedOrder').add_product(variant); } }; }, @@ -213,14 +210,14 @@ Define: pos_product_template.VariantListWidget }, filter_variant: function(){ - value_list = [] + var value_list = []; for (var item in this.filters){ value_list.push(parseInt(this.filters[item])); } this.filter_variant_list = []; - for (index in this.variant_list){ - variant = this.variant_list[index]; - found = true; + for (var index in this.variant_list){ + var variant = this.variant_list[index]; + var found = true; for (var i = 0; i < value_list.length; i++){ found = found && (variant.attribute_value_ids.indexOf(value_list[i]) != -1); } @@ -267,13 +264,14 @@ Define: pos_product_template.VariantListWidget }, }); - -/* ******************************************************** -Define: pos_product_template.AttributeListWidget - - - This widget will display a list of Attribute; -*********************************************************** */ - module.AttributeListWidget = module.PosBaseWidget.extend({ + + + /* ******************************************************** + Define: pos_product_template.AttributeListWidget + + - This widget will display a list of Attribute; + ***********************************************************/ + var AttributeListWidget = PosBaseWidget.extend({ template:'AttributeListWidget', init: function(parent, options) { @@ -281,12 +279,12 @@ Define: pos_product_template.AttributeListWidget this.attribute_list = []; this.product_template = null; this.click_set_attribute_handler = function(event){ - /*TODO: Refactor this function with elegant DOM manipulation */ + //TODO: Refactor this function with elegant DOM manipulation // remove selected item parent = this.parentElement.parentElement.parentElement; parent.children[0].classList.remove('selected'); for (var i = 0 ; i < parent.children[1].children[0].children.length; i ++){ - elem = parent.children[1].children[0].children[i]; + var elem = parent.children[1].children[0].children[i]; elem.children[0].classList.remove('selected'); } // add selected item @@ -294,12 +292,12 @@ Define: pos_product_template.AttributeListWidget self.__parentedParent.variant_list_widget.set_filter(this.dataset['attributeId'], this.dataset['attributeValueId']); }; this.click_reset_attribute_handler = function(event){ - /*TODO: Refactor this function with elegant DOM manipulation */ + //TODO: Refactor this function with elegant DOM manipulation // remove selected item parent = this.parentElement; parent.children[0].classList.remove('selected'); for (var i = 0 ; i < parent.children[1].children[0].children.length; i ++){ - elem = parent.children[1].children[0].children[i]; + var elem = parent.children[1].children[0].children[i]; elem.children[0].classList.remove('selected'); } // add selected item @@ -357,8 +355,7 @@ Define: pos_product_template.AttributeListWidget value_node = value_node.childNodes[1]; return value_node; }, - - + renderElement: function() { var self = this; var el_html = openerp.qweb.render(this.template, {widget: this}); @@ -374,26 +371,24 @@ Define: pos_product_template.AttributeListWidget for(var i = 0, len = this.attribute_list.length; i < len; i++){ var attribute_node = this.render_attribute(this.attribute_list[i]); attribute_node.querySelector('.attribute-name').addEventListener('click', this.click_reset_attribute_handler); -// attribute_node.addEventListener('click', this.click_reset_attribute_handler); list_container.appendChild(attribute_node); }; }, }); - - -/* ******************************************************** -Overload: point_of_sale.PosDB - -- Add to local storage Product Templates Data. -- Link Product Variants to Product Templates. -- Add an extra field 'is_primary_variant' on product object. the product - will be display on product list, only if it is the primary variant; - Otherwise, the product will be displayed only on Template Screen. -- Add an extra field 'product_variant_count' on product object that - indicates the number of variant of the template of the product. -*********************************************************** */ - module.PosDB = module.PosDB.extend({ + + /* ******************************************************** + Overload: point_of_sale.PosDB + + - Add to local storage Product Templates Data. + - Link Product Variants to Product Templates. + - Add an extra field 'is_primary_variant' on product object. the product + will be display on product list, only if it is the primary variant; + Otherwise, the product will be displayed only on Template Screen. + - Add an extra field 'product_variant_count' on product object that + indicates the number of variant of the template of the product. + ********************************************************** */ + PosDB.include({ init: function(options){ this.template_by_id = {}; this.product_attribute_by_id = {}; @@ -429,12 +424,12 @@ Overload: point_of_sale.PosDB attribute_by_template_id: function(template_id){ - template = this.template_by_id[template_id]; + var template = this.template_by_id[template_id]; return this.attribute_by_attribute_value_ids(template.attribute_value_ids); }, attribute_by_attribute_value_ids: function(value_ids){ - attribute_ids = []; + var attribute_ids = []; for (var i = 0; i < value_ids.length; i++){ var value = this.product_attribute_value_by_id[value_ids[i]]; if (attribute_ids.indexOf(value.attribute_id[0])==-1){ @@ -480,82 +475,74 @@ Overload: point_of_sale.PosDB }, }); - -/* ******************************************************** -Overload: point_of_sale.PosModel - -- Overload module.PosModel.initialize function to load extra-data - - Load 'name' field of model product.product; - - Load product.template model; -*********************************************************** */ - var _initialize_ = module.PosModel.prototype.initialize; - module.PosModel.prototype.initialize = function(session, attributes){ - self = this; - // Add the load of the field product_product.name - // that is the name of the template - // Add the load of attribute values - for (var i = 0 ; i < this.models.length; i++){ - if (this.models[i].model == 'product.product'){ - if (this.models[i].fields.indexOf('name') == -1) { - this.models[i].fields.push('name'); - } - if (this.models[i].fields.indexOf('attribute_value_ids') == -1) { - this.models[i].fields.push('attribute_value_ids'); - } - } + /********************************************************* + Overload: point_of_sale.PosModel + + - Overload module.PosModel.initialize function to load extra-data + - Load 'name' field of model product.product; + - Load product.template model; + *********************************************************** */ + // change product.product call + models.PosModel.prototype.models.some(function (model) { + if (model.model !== 'product.product') { + return false; } + // add name and attribute_value_ids to list of fields + // to fetch for product.product + ['name', 'attribute_value_ids'].forEach(function (field) { + if (model.fields.indexOf(field) == -1) { + model.fields.push(field); + } + }); + return true; //exit early the iteration of this.models + }); - // Load Product Template - model = { - model: 'product.template', - fields: [ - 'name', - 'display_name', - 'product_variant_ids', - 'product_variant_count', - ], - domain: function(self){ - return [ - ['sale_ok','=',true], - ['available_in_pos','=',true], - ];}, - context: function(self){ - return { - pricelist: self.pricelist.id, - display_default_code: false, - };}, - loaded: function(self, templates){ - self.db.add_templates(templates); - }, - } - this.models.push(model); - - // Load Product Attribute - model = { - model: 'product.attribute', - fields: [ - 'name', - 'value_ids', - ], - loaded: function(self, attributes){ - self.db.add_product_attributes(attributes); - }, - } - this.models.push(model); - - // Load Product Attribute Value - model = { - model: 'product.attribute.value', - fields: [ - 'name', - 'attribute_id', + //Add our new models + models.PosModel.prototype.models.push({ + model: 'product.template', + fields: [ + 'name', + 'display_name', + 'product_variant_ids', + 'product_variant_count', ], - loaded: function(self, values){ - self.db.add_product_attribute_values(values); - }, - } - this.models.push(model); - - return _initialize_.call(this, session, attributes); + domain: function(self){ + return [ + ['sale_ok','=',true], + ['available_in_pos','=',true], + ];}, + context: function(self){ + return { + pricelist: self.pricelist.id, + display_default_code: false, + };}, + loaded: function(self, templates){ + self.db.add_templates(templates); + }, + }, + { + model: 'product.attribute', + fields: [ + 'name', + 'value_ids', + ], + loaded: function(self, attributes){ + self.db.add_product_attributes(attributes); + }, + }, + { + model: 'product.attribute.value', + fields: [ + 'name', + 'attribute_id', + ], + loaded: function(self, values){ + self.db.add_product_attribute_values(values); + }, + }); + return { + 'SelectVariantPopupWidget': SelectVariantPopupWidget, + 'VariantListWidget': VariantListWidget, + 'AttributeListWidget': AttributeListWidget, }; -}; +}); diff --git a/pos_product_template/static/src/xml/ppt.xml b/pos_product_template/static/src/xml/ppt.xml index 03a2ad7e..7852cb58 100644 --- a/pos_product_template/static/src/xml/ppt.xml +++ b/pos_product_template/static/src/xml/ppt.xml @@ -1,25 +1,7 @@ - - - - - - - - - - - - - - - - - - - - - +