diff --git a/pos_product_template/README.rst b/pos_product_template/README.rst new file mode 100644 index 0000000000..26ae13739f --- /dev/null +++ b/pos_product_template/README.rst @@ -0,0 +1,116 @@ +====================== +POS - Product Template +====================== + +.. + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! This file is generated by oca-gen-addon-readme !! + !! changes will be overwritten. !! + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + !! source digest: sha256:db57e93b7dc8c62c24c51e03b823c331eac215f8938027e6c0949f97e7f6e202 + !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png + :target: https://odoo-community.org/page/development-status + :alt: Beta +.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png + :target: http://www.gnu.org/licenses/agpl-3.0-standalone.html + :alt: License: AGPL-3 +.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fpos-lightgray.png?logo=github + :target: https://github.com/OCA/pos/tree/15.0/pos_product_template + :alt: OCA/pos +.. |badge4| image:: https://img.shields.io/badge/weblate-Translate%20me-F47D42.png + :target: https://translation.odoo-community.org/projects/pos-15-0/pos-15-0-pos_product_template + :alt: Translate me on Weblate +.. |badge5| image:: https://img.shields.io/badge/runboat-Try%20me-875A7B.png + :target: https://runboat.odoo-community.org/builds?repo=OCA/pos&target_branch=15.0 + :alt: Try me on Runboat + +|badge1| |badge2| |badge3| |badge4| |badge5| + + * 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; + +**Table of contents** + +.. contents:: + :local: + +Usage +===== + +Open the Point of Sale, search an article with variants. +You will see one article instead of all the variants. + +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 to smash it by providing a detailed and welcomed +`feedback `_. + +Do not contact contributors directly about support or help with technical issues. + +Credits +======= + +Authors +~~~~~~~ + +* Akretion + +Contributors +~~~~~~~~~~~~ + +* Sylvain LE GAL (https://twitter.com/legalsylvain) +* Navarromiguel (https://github.com/navarromiguel) +* Damendieta (https://github.com/damendieta) +* Raphaël Reverdy (https://akretion.com) +* Pedro Guirao (https://ingenieriacloud.com) + +* `Ooops `_: + + * Giovanni Serra + +* `Aures Tic `_: + + * Jose Zambudio + +Funders +------- + +The development of this module has been financially supported by: + +* Akretion + +Maintainers +~~~~~~~~~~~ + +This module is maintained by the OCA. + +.. image:: https://odoo-community.org/logo.png + :alt: Odoo Community Association + :target: https://odoo-community.org + +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. + +This module is part of the `OCA/pos `_ project on GitHub. + +You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute. diff --git a/pos_product_template/__init__.py b/pos_product_template/__init__.py new file mode 100644 index 0000000000..0650744f6b --- /dev/null +++ b/pos_product_template/__init__.py @@ -0,0 +1 @@ +from . import models diff --git a/pos_product_template/__manifest__.py b/pos_product_template/__manifest__.py new file mode 100644 index 0000000000..7767677333 --- /dev/null +++ b/pos_product_template/__manifest__.py @@ -0,0 +1,30 @@ +{ + "name": "POS - Product Template", + "version": "16.0.1.0.0", + "category": "Point Of Sale", + "author": "Zurab Kituashvili", + "summary": "Manage Product Template in Front End Point Of Sale", + "website": "https://github.com/OCA/pos", + "license": "AGPL-3", + "depends": [ + "point_of_sale", + ], + "data": [ + "views/pos_config_view.xml", + ], + "assets": { + "web.assets_qweb": ["pos_product_template/static/src/xml/**/*.xml"], + "point_of_sale.assets": [ + "pos_product_template/static/src/**/*.js", + "pos_product_template/static/src/css/ppt.css", + ], + }, + "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..44d8b9309f --- /dev/null +++ b/pos_product_template/i18n/es.po @@ -0,0 +1,80 @@ +# 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: 2023-07-26 13:10+0000\n" +"Last-Translator: Anna Martínez \n" +"Language-Team: \n" +"Language: es\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.17\n" + +#. module: pos_product_template +#: model:product.attribute.value,name:pos_product_template.attribute_wifi_extra +msgid "2.399GHz" +msgstr "2.399GHz" + +#. module: pos_product_template +#. openerp-web +#: code:addons/pos_product_template/static/src/xml/SelectVariantPopup.xml:0 +#, python-format +msgid "Cancel" +msgstr "Cancelar" + +#. module: pos_product_template +#. openerp-web +#: code:addons/pos_product_template/static/src/xml/SelectVariantPopup.xml:0 +#, python-format +msgid "Confirm" +msgstr "Confirmar" + +#. module: pos_product_template +#: model:ir.model.fields,field_description:pos_product_template.field_pos_config__display_name +msgid "Display Name" +msgstr "Nombre a mostrar" + +#. module: pos_product_template +#: model:ir.model.fields,field_description:pos_product_template.field_pos_config__id +msgid "ID" +msgstr "ID" + +#. module: pos_product_template +#: model:ir.model.fields,help:pos_product_template.field_pos_config__iface_product_template_show_variants +msgid "" +"If selected the product variant selection screen will show the variants, " +"else it will only allow to confirm once all the attributes are chosen." +msgstr "" +"Si de marca, la pantalla de selección de productos mostrará las variantes, " +"de lo contrario, solo permitirá confirmar una vez elegidos todos los " +"atributos." + +#. module: pos_product_template +#: model:ir.model.fields,field_description:pos_product_template.field_pos_config____last_update +msgid "Last Modified on" +msgstr "Última Modificación en" + +#. module: pos_product_template +#: model:ir.model,name:pos_product_template.model_pos_config +msgid "Point of Sale Configuration" +msgstr "Configuración del punto de venta" + +#. module: pos_product_template +#: model:ir.model.fields,field_description:pos_product_template.field_pos_config__iface_product_template_show_variants +msgid "Product template show variants" +msgstr "La plantilla de producto mostrará las variantes" + +#, python-format +#~ msgid "Variant Selection of" +#~ msgstr "Seleccina una variante de" + +#, 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..398648869f --- /dev/null +++ b/pos_product_template/i18n/fr.po @@ -0,0 +1,76 @@ +# 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/SelectVariantPopup.xml:0 +#, python-format +msgid "Cancel" +msgstr "Annuler" + +#. module: pos_product_template +#. openerp-web +#: code:addons/pos_product_template/static/src/xml/SelectVariantPopup.xml:0 +#, python-format +msgid "Confirm" +msgstr "" + +#. module: pos_product_template +#: model:ir.model.fields,field_description:pos_product_template.field_pos_config__display_name +msgid "Display Name" +msgstr "" + +#. module: pos_product_template +#: model:ir.model.fields,field_description:pos_product_template.field_pos_config__id +msgid "ID" +msgstr "" + +#. module: pos_product_template +#: model:ir.model.fields,help:pos_product_template.field_pos_config__iface_product_template_show_variants +msgid "" +"If selected the product variant selection screen will show the variants, " +"else it will only allow to confirm once all the attributes are chosen." +msgstr "" + +#. module: pos_product_template +#: model:ir.model.fields,field_description:pos_product_template.field_pos_config____last_update +msgid "Last Modified on" +msgstr "" + +#. module: pos_product_template +#: model:ir.model,name:pos_product_template.model_pos_config +msgid "Point of Sale Configuration" +msgstr "" + +#. module: pos_product_template +#: model:ir.model.fields,field_description:pos_product_template.field_pos_config__iface_product_template_show_variants +msgid "Product template show variants" +msgstr "" + +#, python-format +#~ msgid "Variant Selection of" +#~ msgstr "Sélection d'une variante de" + +#, python-format +#~ msgid "Variants" +#~ msgstr "Variantes" diff --git a/pos_product_template/i18n/it.po b/pos_product_template/i18n/it.po new file mode 100644 index 0000000000..f369dbf313 --- /dev/null +++ b/pos_product_template/i18n/it.po @@ -0,0 +1,71 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_product_template +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 14.0\n" +"Report-Msgid-Bugs-To: \n" +"PO-Revision-Date: 2023-03-16 13:22+0000\n" +"Last-Translator: mymage \n" +"Language-Team: none\n" +"Language: it\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: \n" +"Plural-Forms: nplurals=2; plural=n != 1;\n" +"X-Generator: Weblate 4.14.1\n" + +#. module: pos_product_template +#: model:product.attribute.value,name:pos_product_template.attribute_wifi_extra +msgid "2.399GHz" +msgstr "2.399GHz" + +#. module: pos_product_template +#. openerp-web +#: code:addons/pos_product_template/static/src/xml/SelectVariantPopup.xml:0 +#, python-format +msgid "Cancel" +msgstr "Annulla" + +#. module: pos_product_template +#. openerp-web +#: code:addons/pos_product_template/static/src/xml/SelectVariantPopup.xml:0 +#, python-format +msgid "Confirm" +msgstr "Conferma" + +#. module: pos_product_template +#: model:ir.model.fields,field_description:pos_product_template.field_pos_config__display_name +msgid "Display Name" +msgstr "Nome visualizzato" + +#. module: pos_product_template +#: model:ir.model.fields,field_description:pos_product_template.field_pos_config__id +msgid "ID" +msgstr "ID" + +#. module: pos_product_template +#: model:ir.model.fields,help:pos_product_template.field_pos_config__iface_product_template_show_variants +msgid "" +"If selected the product variant selection screen will show the variants, " +"else it will only allow to confirm once all the attributes are chosen." +msgstr "" +"Se selezionato la schermata di selezione variante prodotto visualizzerà le " +"varianti, altrimenti consentirà solo la confema una volta che tutti gli " +"attributi saranno scelti." + +#. module: pos_product_template +#: model:ir.model.fields,field_description:pos_product_template.field_pos_config____last_update +msgid "Last Modified on" +msgstr "Ultima modifica il" + +#. module: pos_product_template +#: model:ir.model,name:pos_product_template.model_pos_config +msgid "Point of Sale Configuration" +msgstr "Configurazione punto vendita" + +#. module: pos_product_template +#: model:ir.model.fields,field_description:pos_product_template.field_pos_config__iface_product_template_show_variants +msgid "Product template show variants" +msgstr "Visualizza varianti modello prodotto" 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..684ddfa560 --- /dev/null +++ b/pos_product_template/i18n/pos_product_template.pot @@ -0,0 +1,50 @@ +# Translation of Odoo Server. +# This file contains the translation of the following modules: +# * pos_product_template +# +msgid "" +msgstr "" +"Project-Id-Version: Odoo Server 15.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/SelectVariantPopup.xml:0 +#, python-format +msgid "Cancel" +msgstr "" + +#. module: pos_product_template +#. openerp-web +#: code:addons/pos_product_template/static/src/xml/SelectVariantPopup.xml:0 +#, python-format +msgid "Confirm" +msgstr "" + +#. module: pos_product_template +#: model:ir.model.fields,help:pos_product_template.field_pos_config__iface_product_template_show_variants +msgid "" +"If selected the product variant selection screen will show the variants, " +"else it will only allow to confirm once all the attributes are chosen." +msgstr "" + +#. module: pos_product_template +#: model:ir.model,name:pos_product_template.model_pos_config +msgid "Point of Sale Configuration" +msgstr "" + +#. module: pos_product_template +#: model:ir.model.fields,field_description:pos_product_template.field_pos_config__iface_product_template_show_variants +msgid "Product template show variants" +msgstr "" diff --git a/pos_product_template/models/__init__.py b/pos_product_template/models/__init__.py new file mode 100644 index 0000000000..db8634ade1 --- /dev/null +++ b/pos_product_template/models/__init__.py @@ -0,0 +1 @@ +from . import pos_config diff --git a/pos_product_template/models/pos_config.py b/pos_product_template/models/pos_config.py new file mode 100644 index 0000000000..25aaa81596 --- /dev/null +++ b/pos_product_template/models/pos_config.py @@ -0,0 +1,19 @@ +# Copyright 2022 Akretion (https://www.akretion.com). +# @author Pierrick Brun +# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). + +from odoo import fields, models + + +class PosConfig(models.Model): + _inherit = "pos.config" + + epson_printer_ip = fields.Char(string="Epson Printer IP") + pos_epson_printer_ip = fields.Char(string="POS Printer IP") + + iface_product_template_show_variants = fields.Boolean( + string="Product template show variants", + default=True, + help="If selected the product variant selection screen will show the variants," + " else it will only allow to confirm once all the attributes are chosen.", + ) diff --git a/pos_product_template/readme/CONTRIBUTORS.rst b/pos_product_template/readme/CONTRIBUTORS.rst new file mode 100644 index 0000000000..ae061bc591 --- /dev/null +++ b/pos_product_template/readme/CONTRIBUTORS.rst @@ -0,0 +1,20 @@ +* Sylvain LE GAL (https://twitter.com/legalsylvain) +* Navarromiguel (https://github.com/navarromiguel) +* Damendieta (https://github.com/damendieta) +* Raphaël Reverdy (https://akretion.com) +* Pedro Guirao (https://ingenieriacloud.com) + +* `Ooops `_: + + * Giovanni Serra + +* `Aures Tic `_: + + * Jose Zambudio + +Funders +------- + +The development of this module has been financially supported by: + +* Akretion diff --git a/pos_product_template/readme/DESCRIPTION.rst b/pos_product_template/readme/DESCRIPTION.rst new file mode 100644 index 0000000000..61b42e59a7 --- /dev/null +++ b/pos_product_template/readme/DESCRIPTION.rst @@ -0,0 +1,11 @@ + * 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; diff --git a/pos_product_template/readme/ROADMAP.rst b/pos_product_template/readme/ROADMAP.rst new file mode 100644 index 0000000000..1ac53b8f14 --- /dev/null +++ b/pos_product_template/readme/ROADMAP.rst @@ -0,0 +1 @@ +* Templates with lot of variants are not shown. See https://github.com/OCA/pos/pull/135 diff --git a/pos_product_template/readme/USAGE.rst b/pos_product_template/readme/USAGE.rst new file mode 100644 index 0000000000..313300eccf --- /dev/null +++ b/pos_product_template/readme/USAGE.rst @@ -0,0 +1,2 @@ +Open the Point of Sale, search an article with variants. +You will see one article instead of all the variants. 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/description/index.html b/pos_product_template/static/description/index.html new file mode 100644 index 0000000000..05a4b428ca --- /dev/null +++ b/pos_product_template/static/description/index.html @@ -0,0 +1,492 @@ + + + + + + +POS - Product Template + + + +
+

POS - Product Template

+ + +

Beta License: AGPL-3 OCA/pos Translate me on Weblate Try me on Runboat

+
+
    +
  • +
    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;
    • +
    +
    +
    +
  • +
+
+

Table of contents

+ +
+

Usage

+

Open the Point of Sale, search an article with variants. +You will see one article instead of all the variants.

+
+
+

Known issues / Roadmap

+ +
+
+

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 to smash it by providing a detailed and welcomed +feedback.

+

Do not contact contributors directly about support or help with technical issues.

+
+
+

Credits

+
+

Authors

+
    +
  • Akretion
  • +
+
+
+

Contributors

+ +
+

Funders

+

The development of this module has been financially supported by:

+
    +
  • Akretion
  • +
+
+
+
+

Maintainers

+

This module is maintained by the OCA.

+Odoo Community Association +

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.

+

This module is part of the OCA/pos project on GitHub.

+

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.

+
+
+
+ + 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..c43605231e --- /dev/null +++ b/pos_product_template/static/src/css/ppt.css @@ -0,0 +1,84 @@ +/* ********* Variant Selector PopUp ************************************** */ +.pos .modal-dialog .select-variant-products { + display: flex; + flex-direction: row; + justify-content: center; + flex-wrap: wrap; + gap: 2px; +} + +.pos .modal-dialog .select-variant-products article { + font-size: 12px; +} + +/* ********* 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.unavailable { + color: grey; +} + +.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; +} 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.esm.js b/pos_product_template/static/src/js/SelectVariantPopup.esm.js new file mode 100644 index 0000000000..93c0809162 --- /dev/null +++ b/pos_product_template/static/src/js/SelectVariantPopup.esm.js @@ -0,0 +1,229 @@ +/** @odoo-module **/ +/* 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). +*/ + +import AbstractAwaitablePopup from "point_of_sale.AbstractAwaitablePopup"; +import Registries from "point_of_sale.Registries"; +import {useListener} from "web.custom_hooks"; +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: {}, + ptav_unavailable_ids: [], + all_attributes_chosen: false, + }); + + this._mountPopup(template); + } + + willUpdateProps(nextProp) { + var template = this.env.pos.db.get_template_by_id(nextProp.template_id); + this._mountPopup(template); + super.willUpdateProps(nextProp); + } + + _mountPopup(template) { + this.state.ptav = []; + this.state.attributes = []; + this.state.template = template; + this.state.products = []; + this.state.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); + + attributes.forEach((attribute) => { + const productAttribute = + this.env.pos.db.product_attribute_by_id[attribute.id]; + attribute.sequence = productAttribute.sequence; + }); + + this.state.ptav = ptav; + this.state.ptav_unavailable_ids = []; + this.state.attributes = attributes.sort((a, b) => + a.sequence > b.sequence ? 1 : b.sequence > a.sequence ? -1 : 0 + ); + 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 (parseInt(ptav_s, 10) !== value_id && avat[ptav_s] === avat[value_id]) { + ptav[ptav_s] = false; + } + }); + + this.state.products = this.refreshProducts(); + var self = this; + var selected_ptav_ids = Object.keys(self.state.ptav_id_selected).filter( + (x) => self.state.ptav_id_selected[x] + ); + this.state.ptav_unavailable_ids = this.state.ptav + .filter(function (value) { + // Remove ptav if no available product corresponds + var res = self.state.products.every(function (product) { + return ( + value.ptav_product_variant_ids.includes(product.id) === false + ); + }); + if (res === true) { + // Do not remove ptav if there is already a ptav chosen for the + // attribute and there are products if changed + // This allows to show the possible modifications of choices + selected_ptav_ids.forEach(function (selected_ptav_id) { + const selected_ptav = self.all_ptav[selected_ptav_id]; + if (value.attribute_id[0] === selected_ptav.attribute_id[0]) { + const selected_ptav_test_list = selected_ptav_ids.filter( + function (id) { + // Test if ptav is available if this ptav is not selected + return id !== selected_ptav_id; + } + ); + const product_ids = Array.from( + self._get_product_ids_for_ptav(selected_ptav_test_list) + ); + res = product_ids.every(function (product_id) { + return ( + value.ptav_product_variant_ids.includes( + product_id + ) === false + ); + }); + } + }); + } + return res; + }) + .map(function (value) { + return value.id; + }); + this.state.all_attributes_chosen = + selected_ptav_ids.length === this.state.attributes.length && + this.state.products.length === 1; + } + async click_confirm() { + if (this.state.all_attributes_chosen !== true) { + throw new Error( + "You can only confirm when all attributes are chosen and there is a variant available" + ); + } + this.product_selected = this.state.products[0]; + return this.confirm(); + } + async getPayload() { + return this.product_selected; + } + _get_product_ids_for_ptav(ptav_list) { + function intersection(setA, setB) { + var elements = new Set(); + for (var elem of setB) { + if (setA.has(elem)) { + elements.add(elem); + } + } + return elements; + } + + function union(setA, setB) { + var elements = new Set(setA); + for (var elem of setB) { + elements.add(elem); + } + return elements; + } + let variants_ids = []; + + if (ptav_list.length === 0) { + variants_ids = this.all_ptav_id + .map((ptav) => { + return new Set(this.all_ptav[ptav].ptav_product_variant_ids); + }) + .reduce((a, b) => { + return union(a, b); + }); + } else { + variants_ids = ptav_list + .map((ptav) => { + return new Set(this.all_ptav[ptav].ptav_product_variant_ids); + }) + .reduce((a, b) => { + return intersection(a, b); + }); + } + return variants_ids; + } + refreshProducts() { + var ptav = Object.keys(this.state.ptav_id_selected).filter( + (x) => this.state.ptav_id_selected[x] + ); + var variants_ids = this._get_product_ids_for_ptav(ptav); + return Array.from(variants_ids).map((x) => { + return this.env.pos.db.get_product_by_id(x); + }); + } +} +Registries.Component.add(SelectVariantPopup); +SelectVariantPopup.template = "SelectVariantPopup"; +export default SelectVariantPopup; diff --git a/pos_product_template/static/src/js/models.esm.js b/pos_product_template/static/src/js/models.esm.js new file mode 100644 index 0000000000..6740cb2c74 --- /dev/null +++ b/pos_product_template/static/src/js/models.esm.js @@ -0,0 +1,191 @@ +/** @odoo-module **/ +/* 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). +*/ + +import PosDB from "point_of_sale.DB"; +import models from "point_of_sale.models"; + +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); + } + }); + // Exit early the iteration of this.models + return true; +}); + +// 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", "sequence"], + 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", + ], + domain: function () { + return [["product_tmpl_id.available_in_pos", "=", true]]; + }, + loaded: function (self, values) { + self.db.add_product_template_attribute_values(values); + }, + }, +]); + +PosDB.include({ + // The maximum number of results returned by a search + product_search_limit: 314159265, + 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 + ) { + 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.esm.js b/pos_product_template/static/src/js/ppt.esm.js new file mode 100644 index 0000000000..3a9007851f --- /dev/null +++ b/pos_product_template/static/src/js/ppt.esm.js @@ -0,0 +1,101 @@ +/** @odoo-module **/ +/* 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). +*/ + +import PosComponent from "point_of_sale.PosComponent"; +import ProductItem from "point_of_sale.ProductItem"; +import ProductScreen from "point_of_sale.ProductScreen"; +import ProductsWidget from "point_of_sale.ProductsWidget"; +import Registries from "point_of_sale.Registries"; +import {useListener} from "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 && ret.payload) + return this._clickProduct({detail: ret.payload}); + } + }; + +const PPTProductsWidget = (ProductsWidget) => + class extends ProductsWidget { + get productsToDisplay() { + 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 { + 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); + +export default { + 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..ad2c7beac4 --- /dev/null +++ b/pos_product_template/static/src/xml/SelectVariantPopup.xml @@ -0,0 +1,78 @@ + + + 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/views/pos_config_view.xml b/pos_product_template/views/pos_config_view.xml new file mode 100644 index 0000000000..5b6fe71f5b --- /dev/null +++ b/pos_product_template/views/pos_config_view.xml @@ -0,0 +1,13 @@ + + + + pos.config.form.view + pos.config + + + + + + + +