diff --git a/pos_product_template/README.rst b/pos_product_template/README.rst new file mode 100644 index 0000000000..40fa6536b6 --- /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/__init__.py b/pos_product_template/__init__.py new file mode 100644 index 0000000000..95ba6a1e99 --- /dev/null +++ b/pos_product_template/__init__.py @@ -0,0 +1,20 @@ +############################################################################## +# +# 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 . +# +############################################################################## diff --git a/pos_product_template/__manifest__.py b/pos_product_template/__manifest__.py new file mode 100644 index 0000000000..373a30ef42 --- /dev/null +++ b/pos_product_template/__manifest__.py @@ -0,0 +1,27 @@ +{ + "name": "POS - Product Template", + "version": "14.0.1.0.0", + "category": "Point Of Sale", + "author": "Akretion,Odoo Community Association (OCA)", + "summary": "Manage Product Template in Front End Point Of Sale", + "website": "https://github.com/OCA/pos", + "license": "AGPL-3", + "depends": [ + "point_of_sale", + ], + "data": [ + "view/view.xml", + ], + "qweb": [ + "static/src/xml/ppt.xml", + "static/src/xml/SelectVariantPopup.xml", + ], + "demo": [ + "demo/product_attribute_value.xml", + "demo/product_product.xml", + ], + "images": [ + "static/src/img/screenshots/pos_product_template.png", + ], + "installable": True, +} diff --git a/pos_product_template/demo/product_attribute_value.xml b/pos_product_template/demo/product_attribute_value.xml new file mode 100644 index 0000000000..bb3b6f0996 --- /dev/null +++ b/pos_product_template/demo/product_attribute_value.xml @@ -0,0 +1,31 @@ + + + + + + + 2.399GHz + + + diff --git a/pos_product_template/demo/product_product.xml b/pos_product_template/demo/product_product.xml new file mode 100644 index 0000000000..5b1ee99c85 --- /dev/null +++ b/pos_product_template/demo/product_product.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/pos_product_template/i18n/es.po b/pos_product_template/i18n/es.po new file mode 100644 index 0000000000..5d6bf4a6eb --- /dev/null +++ b/pos_product_template/i18n/es.po @@ -0,0 +1,44 @@ +# 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" +"Language: \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 +#: model:product.attribute.value,name:pos_product_template.attribute_wifi_extra +msgid "2.399GHz" +msgstr "" + +#. module: pos_product_template +#. openerp-web +#: code:addons/pos_product_template/static/src/xml/ppt.xml:13 +#, python-format +msgid "Cancel" +msgstr "Cancelar" + +#. module: pos_product_template +#. openerp-web +#: code:addons/pos_product_template/static/src/xml/ppt.xml:11 +#, 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:39 +#: code:addons/pos_product_template/static/src/xml/ppt.xml:77 +#, python-format +msgid "Variants" +msgstr "Variantes" diff --git a/pos_product_template/i18n/fr.po b/pos_product_template/i18n/fr.po new file mode 100644 index 0000000000..00a212217b --- /dev/null +++ b/pos_product_template/i18n/fr.po @@ -0,0 +1,44 @@ +# 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 11:57+0000\n" +"PO-Revision-Date: 2014-12-18 11:57+0000\n" +"Last-Translator: <>\n" +"Language-Team: \n" +"Language: \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 +#: model:product.attribute.value,name:pos_product_template.attribute_wifi_extra +msgid "2.399GHz" +msgstr "" + +#. module: pos_product_template +#. openerp-web +#: code:addons/pos_product_template/static/src/xml/ppt.xml:13 +#, python-format +msgid "Cancel" +msgstr "Annuler" + +#. module: pos_product_template +#. openerp-web +#: code:addons/pos_product_template/static/src/xml/ppt.xml:11 +#, python-format +msgid "Variant Selection of" +msgstr "Sélection d'une variante de" + +#. module: pos_product_template +#. openerp-web +#: code:addons/pos_product_template/static/src/xml/ppt.xml:39 +#: code:addons/pos_product_template/static/src/xml/ppt.xml:77 +#, python-format +msgid "Variants" +msgstr "Variantes" diff --git a/pos_product_template/i18n/pos_product_template.pot b/pos_product_template/i18n/pos_product_template.pot new file mode 100644 index 0000000000..ab187f58e4 --- /dev/null +++ b/pos_product_template/i18n/pos_product_template.pot @@ -0,0 +1,42 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_product_template +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 10.0\n" +"Report-Msgid-Bugs-To: \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 +#: model:product.attribute.value,name:pos_product_template.attribute_wifi_extra +msgid "2.399GHz" +msgstr "" + +#. module: pos_product_template +#. openerp-web +#: code:addons/pos_product_template/static/src/xml/ppt.xml:13 +#, python-format +msgid "Cancel" +msgstr "" + +#. module: pos_product_template +#. openerp-web +#: code:addons/pos_product_template/static/src/xml/ppt.xml:11 +#, python-format +msgid "Variant Selection of" +msgstr "" + +#. module: pos_product_template +#. openerp-web +#: code:addons/pos_product_template/static/src/xml/ppt.xml:39 +#: code:addons/pos_product_template/static/src/xml/ppt.xml:77 +#, python-format +msgid "Variants" +msgstr "" + diff --git a/pos_product_template/static/description/icon.png b/pos_product_template/static/description/icon.png new file mode 100644 index 0000000000..4e54d92ab0 Binary files /dev/null and b/pos_product_template/static/description/icon.png differ diff --git a/pos_product_template/static/src/css/ppt.css b/pos_product_template/static/src/css/ppt.css new file mode 100644 index 0000000000..951692649a --- /dev/null +++ b/pos_product_template/static/src/css/ppt.css @@ -0,0 +1,223 @@ +/****************************************************************************** + 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) + + Some part of code from Odoo Project, + Copyright (C) 2004-Today Odoo SA. (). + + 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 . +******************************************************************************/ + +/* ********* Variant Selector PopUp ************************************** */ +.pos .modal-dialog .popup-select-variant { + margin-left: 0; + margin-top: 0; + left: 5%; + width: 90%; + top: 5%; + height: 85%; +} + +.pos .modal-dialog .popup-select-variant .variant-title { + height: 8%; +} + +.pos .modal-dialog .popup-select-variant .variant-title #variant-popup-cancel { + margin: -10px 3px 3px 3px; +} + +.pos .modal-dialog .popup-select-variant .container-attribute-list { + width: 100%; + height: 40%; + overflow: hidden; + overflow-y: auto; + border-bottom: 1px dashed #444; + -webkit-overflow-scrolling: touch; +} + +.pos .modal-dialog .popup-select-variant .container-variant-list { + width: 100%; + height: 50%; + overflow: hidden; + overflow-y: auto; + + -webkit-overflow-scrolling: touch; +} + +.pos .variant-list { + padding: 10px; + text-align: left; + -webkit-transform: translate3d(0, 0, 0); +} + +/* ********* Attribut Display ******************************************** */ +.pos .attribute { + position: relative; + width: 100%; + vertical-align: top; + display: inline-block; +} + +.pos .attribute .attribute-name { + float: left; + font-size: 13px; + width: 20%; + overflow: hidden; +} + +.pos .attribute .value-list-container { + width: 78%; + float: left; +} + +.pos .attribute .selected { + background-color: white; +} + +.attributeValue.selected { + color: red; +} + +.pos .attribute .attribute-value { + float: left; + width: 150px; + padding-left: 15px; +} + +.pos .attribute .attribute-value .button { + width: 100%; +} + +.pos .attribute .attribute-value .attribute-value-header { + width: 100%; + height: 15px; + text-align: right; +} +.pos .attribute .attribute-value .attribute-value-name { + width: 100%; + height: 25px; + line-height: 25px; + overflow: hidden; + font-size: 10px; + text-align: left; + font-weight: normal; + padding: 2px; +} + +.pos .attribute .attribute-value .variant-quantity { + position: relative; + top: 2px; + right: 0px; + vertical-align: top; + color: #fff; + line-height: 13px; + background: none repeat scroll 0% 0% #7f82ac; + padding: 2px 5px; + border-radius: 2px; + font-size: 9px; + margin-right: 3px; +} + +/* ********* Variant Display ********************************************* */ +/* This part is a copy paste from Odoo Project, with some values changed */ +.pos .variant { + position: relative; + vertical-align: top; + display: inline-block; + line-height: 100px; + font-size: 11px; + margin: 5px !important; + width: 240px; + height: 60px; + background: #fff; + border: 1px solid #e2e2e2; + border-radius: 3px; + border-bottom-width: 3px; + overflow: hidden; + cursor: pointer; +} + +.pos .variant .variant-header { + position: relative; + width: 240px; + height: 30px; + background: white; + text-align: center; +} + +.pos .variant .price-tag { + position: absolute; + top: 2px; + right: 2px; + vertical-align: top; + color: white; + line-height: 13px; + background: #7f82ac; + padding: 2px 5px; + border-radius: 2px; +} + +.pos .variant .variant-name { + font-weight: normal; + 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%; + overflow: hidden; + text-overflow: ellipsis; + 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; +} + +/*.pos .variant-list-scroller{*/ +/* -webkit-box-sizing: border-box;*/ +/* -moz-box-sizing: border-box;*/ +/* -ms-box-sizing: border-box;*/ +/* box-sizing: border-box;*/ +/* width:50%;*/ +/* height:50%;*/ +/* overflow: hidden;*/ +/* overflow-y: auto;*/ +/* -webkit-overflow-scrolling: touch;*/ +/* -webkit-transform: translate3d(0,0,0);*/ + +/*}*/ diff --git a/pos_product_template/static/src/img/screenshots/pos_product_template.png b/pos_product_template/static/src/img/screenshots/pos_product_template.png new file mode 100644 index 0000000000..288053e0b4 Binary files /dev/null and b/pos_product_template/static/src/img/screenshots/pos_product_template.png differ diff --git a/pos_product_template/static/src/js/SelectVariantPopup.js b/pos_product_template/static/src/js/SelectVariantPopup.js new file mode 100644 index 0000000000..774fe8f313 --- /dev/null +++ b/pos_product_template/static/src/js/SelectVariantPopup.js @@ -0,0 +1,156 @@ +/* Copyright (C) 2020-Today Akretion (https://www.akretion.com) + @author Raphaël Reverdy (https://www.akretion.com) + License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl). +*/ + +odoo.define("pos_product_template.SelectVariantPopup", function (require) { + "use strict"; + + const AbstractAwaitablePopup = require("point_of_sale.AbstractAwaitablePopup"); + const {useListener} = require("web.custom_hooks"); + const Registries = require("point_of_sale.Registries"); + const {useState} = owl.hooks; + + class SelectVariantPopup extends AbstractAwaitablePopup { + constructor(parent, props) { + super(parent, props); + var template = this.env.pos.db.get_template_by_id(props.template_id); + this.state = useState({ + ptav: [], + attributes: [], + template: template, + products: [], + ptav_id_selected: {}, + }); + var ptav = Array.from( + new Set( + template.product_template_attribute_value_ids.map((x) => + this.env.pos.db.get_product_template_attribute_value_by_id(x) + ) + ) + ); + + var attributes_by_id = {}; + ptav.forEach((x) => { + var id = x.attribute_id[0]; + var value_id = x.product_attribute_value_id[0]; + attributes_by_id[id] = attributes_by_id[id] || { + attribute: x.attribute_id, + values_id: {}, + ptav: {}, + }; + attributes_by_id[id].values_id[value_id] = true; + attributes_by_id[id].ptav[x.id] = x; + }); + var attributes = Object.values(attributes_by_id).map((x) => { + x.ptav = Object.values(x.ptav); + x.id = x.attribute[0]; + x.name = x.attribute[1]; + return x; + }); + this.all_ptav_id = ptav.map((x) => x.id); + this.all_ptav = {}; + + ptav.forEach((x) => (this.all_ptav[x.id] = x)); + + var products = this.refreshProducts(); + useListener("click-product", this._clickProduct); + useListener("click-attribute-value", this._clickAttributeValue); + + this.state.ptav = ptav; + this.state.attributes = attributes; + this.state.products = products; + } + + async _clickProduct(event) { + this.product_selected = event.detail; + return this.confirm(); + } + async _clickAttributeValue(event) { + var value_id = event.detail.id; + // Init + var ptav = this.state.ptav_id_selected; + ptav[value_id] = ptav[value_id] || false; + // Toggle + ptav[value_id] = !ptav[value_id]; + + var avat = {}; + var attributes_stringify = JSON.parse( + JSON.stringify(this.state.attributes) + ); + _.each(attributes_stringify, function (at) { + _.each(at.ptav, function (av) { + avat[av.id] = at.id; + }); + }); + + var ptav_stringify = JSON.parse( + JSON.stringify(this.state.ptav_id_selected) + ); + _.each(Object.keys(ptav_stringify), function (ptav_s) { + if (ptav_s != value_id && avat[ptav_s] == avat[value_id]) { + ptav[ptav_s] = false; + } + }); + + this.state.products = this.refreshProducts(); + } + async getPayload() { + return this.product_selected; + } + + refreshProducts() { + function intersection(setA, setB) { + var intersection = new Set(); + for (var elem of setB) { + if (setA.has(elem)) { + intersection.add(elem); + } + } + return intersection; + } + + function union(setA, setB) { + var union = new Set(setA); + for (var elem of setB) { + union.add(elem); + } + return union; + } + + var ptav = Object.keys(this.state.ptav_id_selected).filter( + (x) => this.state.ptav_id_selected[x] + ); + if (!ptav.length) { + ptav = this.all_ptav_id; + var variants_ids = ptav + .map((ptav) => { + return new Set(this.all_ptav[ptav].ptav_product_variant_ids); + }) + .reduce((a, b) => { + return union(a, b); + }); + } else { + var variants_ids = ptav + .map((ptav) => { + return new Set(this.all_ptav[ptav].ptav_product_variant_ids); + }) + .reduce((a, b) => { + return intersection(a, b); + }); + } + return Array.from(variants_ids).map((x) => { + return this.env.pos.db.get_product_by_id(x); + }); + } + } + SelectVariantPopup.template = "SelectVariantPopup"; + SelectVariantPopup.defaultProps = { + confirmText: "Ok", + cancelText: "Cancel", + body: "", + }; + + Registries.Component.add(SelectVariantPopup); + return SelectVariantPopup; +}); diff --git a/pos_product_template/static/src/js/models.js b/pos_product_template/static/src/js/models.js new file mode 100644 index 0000000000..ecc76724f8 --- /dev/null +++ b/pos_product_template/static/src/js/models.js @@ -0,0 +1,188 @@ +/* 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). +*/ + +odoo.define("pos_product_template.models", function (require) { + "use strict"; + + var models = require("point_of_sale.models"); + var PosDB = require("point_of_sale.DB"); + + models.PosModel.prototype.models.some(function (model) { + if (model.model !== "product.product") { + return false; + } + // Add name and product_template_attribute_value_ids to list of fields + // to fetch for product.product + ["name", "product_template_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 + }); + + // Add our new models + models.load_models([ + { + model: "product.template", + fields: [ + "name", + "display_name", + "product_variant_ids", + "product_variant_count", + ], + domain: function () { + return [ + ["sale_ok", "=", true], + ["available_in_pos", "=", true], + ]; + }, + context: function (self) { + return { + pricelist: self.pricelists[0].id, + display_default_code: false, + }; + }, + loaded: function (self, templates) { + // If pos_cache + if (Object.keys(self.db.product_by_id).length > 0) { + self.db.add_templates(templates); + } else { + self.db.raw_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); + }, + }, + { + model: "product.template.attribute.value", + fields: [ + "name", + "attribute_id", + "product_tmpl_id", + "product_attribute_value_id", + "ptav_product_variant_ids", + ], + loaded: function (self, values) { + self.db.add_product_template_attribute_values(values); + }, + }, + ]); + + PosDB.include({ + product_search_limit: 314159265, // The maximum number of results returned by a search + product_display_limit: 10, + // Can't change limit because it's also used in partner search + init: function (options) { + this.template_by_id = {}; + this.product_attribute_by_id = {}; + this.product_attribute_value_by_id = {}; + this.product_template_attribute_value_by_id = {}; + this._super(options); + }, + get_product_by_category: function (category_id) { + // Change the limit only the time of the search + this.product_display_limit = this.limit; + this.limit = this.product_search_limit; + var res = this._super(category_id); + this.limit = this.product_display_limit; + return res; + }, + search_product_in_category: function (category_id, query) { + // Change the limit only the time of the search + this.product_display_limit = this.limit; + this.limit = this.product_search_limit; + var res = this._super(category_id, query); + this.limit = this.product_display_limit; + return res; + }, + add_products: function (products) { + this._super(products); + // If pos_cache is also installed - then products are not available when product.templates are already loaded + // so we have to re add them here + if (this.raw_templates) { + this.add_templates(this.raw_templates); + } + }, + + get_product_template_attribute_value_by_id: function (tmpl_attribute_value_id) { + return this.product_template_attribute_value_by_id[tmpl_attribute_value_id]; + }, + + get_template_by_id: function (id) { + return this.template_by_id[id]; + }, + add_templates: function (templates) { + templates.forEach((template) => { + var product_template_attribute_value_ids = []; + // Store Templates + this.template_by_id[template.id] = template; + + // Update Product information + var tmpl_attribute_value_ids = new Set(); + template.product_variant_ids.forEach((variant_id) => { + var variant = this.get_product_by_id(variant_id); + if (variant != undefined) { + variant.product_template_attribute_value_ids.forEach( + (tmpl_attr_value_id) => { + tmpl_attribute_value_ids.add( + this.get_product_template_attribute_value_by_id( + tmpl_attr_value_id + ) + ); + + // Add ptav + product_template_attribute_value_ids.push( + tmpl_attr_value_id + ); + } + ); + variant.product_variant_count = template.product_variant_count; + variant.template = template; + } + }); + + template.product_template_attribute_value_ids = product_template_attribute_value_ids; + }); + }, + + add_product_attributes: function (product_attributes) { + product_attributes.forEach((product_attribute) => { + this.product_attribute_by_id[product_attribute.id] = product_attribute; + }); + }, + + add_product_attribute_values: function (product_attribute_values) { + product_attribute_values.forEach((attribute_value) => { + this.product_attribute_value_by_id[ + attribute_value.id + ] = attribute_value; + }); + }, + add_product_template_attribute_values: function ( + product_template_attribute_values + ) { + product_template_attribute_values.forEach((attribute_value) => { + this.product_template_attribute_value_by_id[ + attribute_value.id + ] = attribute_value; + }); + }, + }); +}); diff --git a/pos_product_template/static/src/js/ppt.js b/pos_product_template/static/src/js/ppt.js new file mode 100644 index 0000000000..a2e362222a --- /dev/null +++ b/pos_product_template/static/src/js/ppt.js @@ -0,0 +1,117 @@ +/* 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). +*/ + +odoo.define("pos_product_template.pos_product_template", function (require) { + "use strict"; + + var ProductsWidget = require("point_of_sale.ProductsWidget"); + var ProductItem = require("point_of_sale.ProductItem"); + var ProductScreen = require("point_of_sale.ProductScreen"); + const Registries = require("point_of_sale.Registries"); + const PosComponent = require("point_of_sale.PosComponent"); + const {useListener} = require("web.custom_hooks"); + + /* ******************************************************** + 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; + *********************************************************** */ + + const PPTProductScreen = (ProductScreen) => + class extends ProductScreen { + constructor(parent, props) { + super(parent, props); + useListener("click-product-template", this._clickProductTemplate); + } + async _clickProductTemplate(event) { + // Display our select-variant popup when needed + // chain call to clickProduct + var product = event.detail; + var ret = await this.showPopup("SelectVariantPopup", { + template_id: product.product_tmpl_id, + }); + if (ret.confirmed) return this._clickProduct({detail: ret.payload}); + } + }; + + const PPTProductsWidget = (ProductsWidget) => + class extends ProductsWidget { + get productsToDisplay() { + // /* ************************************************ + // 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 tmpl_seen = []; + var res = super.productsToDisplay + .filter(function (product) { + if (tmpl_seen.indexOf(product.product_tmpl_id) === -1) { + // First time we see it, display it + tmpl_seen.push(product.product_tmpl_id); + return true; + } + return false; + }) + .slice(0, this.env.pos.db.product_display_limit); + return res; + } + }; + + const PPTProductItem = (ProductItem) => + class extends ProductItem { + constructor(parent, props) { + // Reuse ProductItem but change only + // the template for product.template + super(parent, props); + if (props.forceVariant) { + // In order to not recurse indefinitly + } else if (props.product.product_variant_count > 1) { + var qweb = this.env.qweb; + this.__owl__.renderFn = qweb.render.bind( + qweb, + "ProductTemplateItem" + ); + } + } + get imageTmpUrl() { + const product = this.props.product; + return `/web/image?model=product.template&field=image_128&id=${product.template.id}&write_date=${product.write_date}&unique=1`; + } + }; + + class AttributeValueItem extends PosComponent { + constructor(parent, props) { + super(parent, props); + } + + spaceClickProduct(event) { + if (event.which === 32) { + this.trigger("click-product", this.props.product); + } + } + } + + AttributeValueItem.template = "AttributeValueItem"; + + Registries.Component.add(AttributeValueItem); + + Registries.Component.extend(ProductScreen, PPTProductScreen); + Registries.Component.extend(ProductItem, PPTProductItem); + Registries.Component.extend(ProductsWidget, PPTProductsWidget); + + return { + PPTProductScreen: PPTProductScreen, + PPTProductItem: PPTProductItem, + PPTProductsWidget: PPTProductsWidget, + AttributeValueItem: AttributeValueItem, + }; +}); diff --git a/pos_product_template/static/src/xml/SelectVariantPopup.xml b/pos_product_template/static/src/xml/SelectVariantPopup.xml new file mode 100644 index 0000000000..9f82eb95e7 --- /dev/null +++ b/pos_product_template/static/src/xml/SelectVariantPopup.xml @@ -0,0 +1,66 @@ + + + diff --git a/pos_product_template/static/src/xml/ppt.xml b/pos_product_template/static/src/xml/ppt.xml new file mode 100644 index 0000000000..4c044d037f --- /dev/null +++ b/pos_product_template/static/src/xml/ppt.xml @@ -0,0 +1,53 @@ + + + diff --git a/pos_product_template/view/view.xml b/pos_product_template/view/view.xml new file mode 100644 index 0000000000..45d808ed5c --- /dev/null +++ b/pos_product_template/view/view.xml @@ -0,0 +1,33 @@ + + + +